From 68a424afac95762eccfb548977904738995b82a4 Mon Sep 17 00:00:00 2001 From: pythonlover02 Date: Wed, 8 Apr 2026 06:47:27 -0300 Subject: [PATCH 1/4] [meta, d3d9, util] port d7vk to sarek needs testing, at least it compiles :/ --- meson_options.txt | 1 + src/d3d9/d3d9_adapter.h | 8 +- src/d3d9/d3d9_bridge.cpp | 38 + src/d3d9/d3d9_bridge.h | 22 + src/d3d9/d3d9_common_texture.cpp | 5 +- src/d3d9/d3d9_device.cpp | 12 +- src/d3d9/d3d9_device.h | 49 + src/d3d9/d3d9_format.cpp | 13 +- src/d3d9/d3d9_format.h | 3 + src/d3d9/d3d9_interface.h | 67 +- src/d3d9/d3d9_options.cpp | 3 + src/d3d9/d3d9_options.h | 5 + src/d3d9/d3d9_state.h | 9 +- src/d3d9/d3d9_swapchain.cpp | 9 +- src/d3d9/d3d9_util.cpp | 32 +- src/d3d9/d3d9_util.h | 46 +- src/d3d9/shaders/d3d9_convert_yuy2_uyvy.comp | 2 +- .../shaders/d3d9_fixed_function_common.glsl | 365 ++++ .../shaders/d3d9_fixed_function_frag.frag | 4 + .../shaders/d3d9_fixed_function_frag.glsl | 722 +++++++ .../d3d9_fixed_function_frag_sample.frag | 6 + .../shaders/d3d9_fixed_function_vert.vert | 725 +++++++ src/ddraw/d3d3/d3d3_device.cpp | 1418 +++++++++++++ src/ddraw/d3d3/d3d3_device.h | 211 ++ src/ddraw/d3d3/d3d3_execute_buffer.cpp | 105 + src/ddraw/d3d3/d3d3_execute_buffer.h | 59 + src/ddraw/d3d3/d3d3_interface.cpp | 337 +++ src/ddraw/d3d3/d3d3_interface.h | 69 + src/ddraw/d3d3/d3d3_material.cpp | 112 + src/ddraw/d3d3/d3d3_material.h | 48 + src/ddraw/d3d3/d3d3_texture.cpp | 141 ++ src/ddraw/d3d3/d3d3_texture.h | 52 + src/ddraw/d3d3/d3d3_viewport.cpp | 438 ++++ src/ddraw/d3d3/d3d3_viewport.h | 72 + src/ddraw/d3d5/d3d5_device.cpp | 1555 ++++++++++++++ src/ddraw/d3d5/d3d5_device.h | 252 +++ src/ddraw/d3d5/d3d5_interface.cpp | 559 +++++ src/ddraw/d3d5/d3d5_interface.h | 69 + src/ddraw/d3d5/d3d5_material.cpp | 95 + src/ddraw/d3d5/d3d5_material.h | 42 + src/ddraw/d3d5/d3d5_texture.cpp | 124 ++ src/ddraw/d3d5/d3d5_texture.h | 48 + src/ddraw/d3d5/d3d5_viewport.cpp | 509 +++++ src/ddraw/d3d5/d3d5_viewport.h | 77 + src/ddraw/d3d6/d3d6_buffer.cpp | 194 ++ src/ddraw/d3d6/d3d6_buffer.h | 120 ++ src/ddraw/d3d6/d3d6_device.cpp | 1851 +++++++++++++++++ src/ddraw/d3d6/d3d6_device.h | 285 +++ src/ddraw/d3d6/d3d6_interface.cpp | 566 +++++ src/ddraw/d3d6/d3d6_interface.h | 75 + src/ddraw/d3d6/d3d6_material.cpp | 95 + src/ddraw/d3d6/d3d6_material.h | 42 + src/ddraw/d3d6/d3d6_texture.cpp | 128 ++ src/ddraw/d3d6/d3d6_texture.h | 48 + src/ddraw/d3d6/d3d6_viewport.cpp | 575 +++++ src/ddraw/d3d6/d3d6_viewport.h | 83 + src/ddraw/d3d7/d3d7_buffer.cpp | 233 +++ src/ddraw/d3d7/d3d7_buffer.h | 114 + src/ddraw/d3d7/d3d7_device.cpp | 1493 +++++++++++++ src/ddraw/d3d7/d3d7_device.h | 229 ++ src/ddraw/d3d7/d3d7_interface.cpp | 418 ++++ src/ddraw/d3d7/d3d7_interface.h | 66 + src/ddraw/d3d7/d3d7_state_block.cpp | 69 + src/ddraw/d3d7/d3d7_state_block.h | 91 + src/ddraw/d3d_common_interface.cpp | 54 + src/ddraw/d3d_common_interface.h | 86 + src/ddraw/d3d_common_material.cpp | 12 + src/ddraw/d3d_common_material.h | 41 + src/ddraw/d3d_common_texture.cpp | 13 + src/ddraw/d3d_common_texture.h | 46 + src/ddraw/d3d_common_viewport.cpp | 61 + src/ddraw/d3d_common_viewport.h | 218 ++ src/ddraw/d3d_light.cpp | 135 ++ src/ddraw/d3d_light.h | 74 + src/ddraw/d3d_multithread.cpp | 12 + src/ddraw/d3d_multithread.h | 77 + src/ddraw/ddraw.def | 23 + src/ddraw/ddraw.sym | 27 + src/ddraw/ddraw/ddraw_interface.cpp | 620 ++++++ src/ddraw/ddraw/ddraw_interface.h | 87 + src/ddraw/ddraw/ddraw_surface.cpp | 1609 ++++++++++++++ src/ddraw/ddraw/ddraw_surface.h | 224 ++ src/ddraw/ddraw2/ddraw2_interface.cpp | 668 ++++++ src/ddraw/ddraw2/ddraw2_interface.h | 85 + src/ddraw/ddraw2/ddraw2_surface.cpp | 1003 +++++++++ src/ddraw/ddraw2/ddraw2_surface.h | 196 ++ src/ddraw/ddraw2/ddraw3_surface.cpp | 1045 ++++++++++ src/ddraw/ddraw2/ddraw3_surface.h | 199 ++ src/ddraw/ddraw4/ddraw4_interface.cpp | 788 +++++++ src/ddraw/ddraw4/ddraw4_interface.h | 96 + src/ddraw/ddraw4/ddraw4_surface.cpp | 1537 ++++++++++++++ src/ddraw/ddraw4/ddraw4_surface.h | 222 ++ src/ddraw/ddraw7/ddraw7_interface.cpp | 778 +++++++ src/ddraw/ddraw7/ddraw7_interface.h | 100 + src/ddraw/ddraw7/ddraw7_surface.cpp | 1742 ++++++++++++++++ src/ddraw/ddraw7/ddraw7_surface.h | 237 +++ src/ddraw/ddraw_caps.h | 37 + src/ddraw/ddraw_class_factory.cpp | 15 + src/ddraw/ddraw_class_factory.h | 44 + src/ddraw/ddraw_clipper.cpp | 53 + src/ddraw/ddraw_clipper.h | 36 + src/ddraw/ddraw_common_interface.cpp | 328 +++ src/ddraw/ddraw_common_interface.h | 337 +++ src/ddraw/ddraw_common_surface.cpp | 61 + src/ddraw/ddraw_common_surface.h | 403 ++++ src/ddraw/ddraw_format.h | 910 ++++++++ src/ddraw/ddraw_gamma.cpp | 104 + src/ddraw/ddraw_gamma.h | 33 + src/ddraw/ddraw_include.h | 284 +++ src/ddraw/ddraw_main.cpp | 567 +++++ src/ddraw/ddraw_options.h | 134 ++ src/ddraw/ddraw_palette.cpp | 54 + src/ddraw/ddraw_palette.h | 41 + src/ddraw/ddraw_util.h | 1639 +++++++++++++++ src/ddraw/ddraw_wrapped_object.h | 95 + src/ddraw/meson.build | 61 + src/ddraw/version.rc | 31 + src/meson.build | 9 +- src/util/config/config.cpp | 767 ++++++- 119 files changed, 33360 insertions(+), 111 deletions(-) create mode 100644 src/d3d9/shaders/d3d9_fixed_function_common.glsl create mode 100644 src/d3d9/shaders/d3d9_fixed_function_frag.frag create mode 100644 src/d3d9/shaders/d3d9_fixed_function_frag.glsl create mode 100644 src/d3d9/shaders/d3d9_fixed_function_frag_sample.frag create mode 100644 src/d3d9/shaders/d3d9_fixed_function_vert.vert create mode 100644 src/ddraw/d3d3/d3d3_device.cpp create mode 100644 src/ddraw/d3d3/d3d3_device.h create mode 100644 src/ddraw/d3d3/d3d3_execute_buffer.cpp create mode 100644 src/ddraw/d3d3/d3d3_execute_buffer.h create mode 100644 src/ddraw/d3d3/d3d3_interface.cpp create mode 100644 src/ddraw/d3d3/d3d3_interface.h create mode 100644 src/ddraw/d3d3/d3d3_material.cpp create mode 100644 src/ddraw/d3d3/d3d3_material.h create mode 100644 src/ddraw/d3d3/d3d3_texture.cpp create mode 100644 src/ddraw/d3d3/d3d3_texture.h create mode 100644 src/ddraw/d3d3/d3d3_viewport.cpp create mode 100644 src/ddraw/d3d3/d3d3_viewport.h create mode 100644 src/ddraw/d3d5/d3d5_device.cpp create mode 100644 src/ddraw/d3d5/d3d5_device.h create mode 100644 src/ddraw/d3d5/d3d5_interface.cpp create mode 100644 src/ddraw/d3d5/d3d5_interface.h create mode 100644 src/ddraw/d3d5/d3d5_material.cpp create mode 100644 src/ddraw/d3d5/d3d5_material.h create mode 100644 src/ddraw/d3d5/d3d5_texture.cpp create mode 100644 src/ddraw/d3d5/d3d5_texture.h create mode 100644 src/ddraw/d3d5/d3d5_viewport.cpp create mode 100644 src/ddraw/d3d5/d3d5_viewport.h create mode 100644 src/ddraw/d3d6/d3d6_buffer.cpp create mode 100644 src/ddraw/d3d6/d3d6_buffer.h create mode 100644 src/ddraw/d3d6/d3d6_device.cpp create mode 100644 src/ddraw/d3d6/d3d6_device.h create mode 100644 src/ddraw/d3d6/d3d6_interface.cpp create mode 100644 src/ddraw/d3d6/d3d6_interface.h create mode 100644 src/ddraw/d3d6/d3d6_material.cpp create mode 100644 src/ddraw/d3d6/d3d6_material.h create mode 100644 src/ddraw/d3d6/d3d6_texture.cpp create mode 100644 src/ddraw/d3d6/d3d6_texture.h create mode 100644 src/ddraw/d3d6/d3d6_viewport.cpp create mode 100644 src/ddraw/d3d6/d3d6_viewport.h create mode 100644 src/ddraw/d3d7/d3d7_buffer.cpp create mode 100644 src/ddraw/d3d7/d3d7_buffer.h create mode 100644 src/ddraw/d3d7/d3d7_device.cpp create mode 100644 src/ddraw/d3d7/d3d7_device.h create mode 100644 src/ddraw/d3d7/d3d7_interface.cpp create mode 100644 src/ddraw/d3d7/d3d7_interface.h create mode 100644 src/ddraw/d3d7/d3d7_state_block.cpp create mode 100644 src/ddraw/d3d7/d3d7_state_block.h create mode 100644 src/ddraw/d3d_common_interface.cpp create mode 100644 src/ddraw/d3d_common_interface.h create mode 100644 src/ddraw/d3d_common_material.cpp create mode 100644 src/ddraw/d3d_common_material.h create mode 100644 src/ddraw/d3d_common_texture.cpp create mode 100644 src/ddraw/d3d_common_texture.h create mode 100644 src/ddraw/d3d_common_viewport.cpp create mode 100644 src/ddraw/d3d_common_viewport.h create mode 100644 src/ddraw/d3d_light.cpp create mode 100644 src/ddraw/d3d_light.h create mode 100644 src/ddraw/d3d_multithread.cpp create mode 100644 src/ddraw/d3d_multithread.h create mode 100644 src/ddraw/ddraw.def create mode 100644 src/ddraw/ddraw.sym create mode 100644 src/ddraw/ddraw/ddraw_interface.cpp create mode 100644 src/ddraw/ddraw/ddraw_interface.h create mode 100644 src/ddraw/ddraw/ddraw_surface.cpp create mode 100644 src/ddraw/ddraw/ddraw_surface.h create mode 100644 src/ddraw/ddraw2/ddraw2_interface.cpp create mode 100644 src/ddraw/ddraw2/ddraw2_interface.h create mode 100644 src/ddraw/ddraw2/ddraw2_surface.cpp create mode 100644 src/ddraw/ddraw2/ddraw2_surface.h create mode 100644 src/ddraw/ddraw2/ddraw3_surface.cpp create mode 100644 src/ddraw/ddraw2/ddraw3_surface.h create mode 100644 src/ddraw/ddraw4/ddraw4_interface.cpp create mode 100644 src/ddraw/ddraw4/ddraw4_interface.h create mode 100644 src/ddraw/ddraw4/ddraw4_surface.cpp create mode 100644 src/ddraw/ddraw4/ddraw4_surface.h create mode 100644 src/ddraw/ddraw7/ddraw7_interface.cpp create mode 100644 src/ddraw/ddraw7/ddraw7_interface.h create mode 100644 src/ddraw/ddraw7/ddraw7_surface.cpp create mode 100644 src/ddraw/ddraw7/ddraw7_surface.h create mode 100644 src/ddraw/ddraw_caps.h create mode 100644 src/ddraw/ddraw_class_factory.cpp create mode 100644 src/ddraw/ddraw_class_factory.h create mode 100644 src/ddraw/ddraw_clipper.cpp create mode 100644 src/ddraw/ddraw_clipper.h create mode 100644 src/ddraw/ddraw_common_interface.cpp create mode 100644 src/ddraw/ddraw_common_interface.h create mode 100644 src/ddraw/ddraw_common_surface.cpp create mode 100644 src/ddraw/ddraw_common_surface.h create mode 100644 src/ddraw/ddraw_format.h create mode 100644 src/ddraw/ddraw_gamma.cpp create mode 100644 src/ddraw/ddraw_gamma.h create mode 100644 src/ddraw/ddraw_include.h create mode 100644 src/ddraw/ddraw_main.cpp create mode 100644 src/ddraw/ddraw_options.h create mode 100644 src/ddraw/ddraw_palette.cpp create mode 100644 src/ddraw/ddraw_palette.h create mode 100644 src/ddraw/ddraw_util.h create mode 100644 src/ddraw/ddraw_wrapped_object.h create mode 100644 src/ddraw/meson.build create mode 100644 src/ddraw/version.rc diff --git a/meson_options.txt b/meson_options.txt index d5b811214e2..acd746ec203 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_ddraw', type : 'boolean', value : true, description: 'Build DDRAW') 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') diff --git a/src/d3d9/d3d9_adapter.h b/src/d3d9/d3d9_adapter.h index 0cf74981971..4ce253241d8 100644 --- a/src/d3d9/d3d9_adapter.h +++ b/src/d3d9/d3d9_adapter.h @@ -87,6 +87,10 @@ namespace dxvk { return m_d3d9Formats.GetUnsupportedFormatInfo(Format); } + void RefreshFormatsTable(bool isD3D8Compatible) const { + m_d3d9Formats.RefreshFormatSupport(isD3D8Compatible); + } + private: HRESULT CheckDeviceVkFormat( @@ -105,8 +109,8 @@ namespace dxvk { std::vector m_modes; D3D9Format m_modeCacheFormat; - const D3D9VkFormatTable m_d3d9Formats; + mutable D3D9VkFormatTable m_d3d9Formats; }; -} \ No newline at end of file +} diff --git a/src/d3d9/d3d9_bridge.cpp b/src/d3d9/d3d9_bridge.cpp index 6f79cb2ba10..47c691cb9ad 100644 --- a/src/d3d9/d3d9_bridge.cpp +++ b/src/d3d9/d3d9_bridge.cpp @@ -29,6 +29,28 @@ namespace dxvk { return m_device->QueryInterface(riid, ppvObject); } + uint32_t DxvkD3D8Bridge::DetermineInitialTextureMemory() { + const int64_t initialTextureMemory = m_device->DetermineInitialTextureMemory(); + return initialTextureMemory > 0 ? static_cast(initialTextureMemory) : 0; + } + + HRESULT DxvkD3D8Bridge::ResetSwapChain(D3DPRESENT_PARAMETERS* Params) { + return m_device->ResetSwapChain(Params, nullptr); + } + + HRESULT DxvkD3D8Bridge::SetColorKeyState(bool colorKeyState) { + return m_device->SetColorKeyState(colorKeyState); + } + + HRESULT DxvkD3D8Bridge::SetColorKey(DWORD colorKeyLow, DWORD colorKeyHigh) { + return m_device->SetColorKey(colorKeyLow, colorKeyHigh); + } + + HRESULT DxvkD3D8Bridge::SetLegacyLightsState(bool legacyLightsState, bool isD3DLight2) { + m_device->SetRenderState(D3DRS_NORMALIZENORMALS, legacyLightsState); + return m_device->SetLegacyLightsState(legacyLightsState, isD3DLight2); + } + HRESULT DxvkD3D8Bridge::UpdateTextureFromBuffer( IDirect3DSurface9* pDestSurface, IDirect3DSurface9* pSrcSurface, @@ -115,6 +137,22 @@ namespace dxvk { return m_interface->QueryInterface(riid, ppvObject); } + void DxvkD3D8InterfaceBridge::EnableD3D3CompatibilityMode() { + m_interface->EnableD3D3CompatibilityMode(); + } + + void DxvkD3D8InterfaceBridge::EnableD3D5CompatibilityMode() { + m_interface->EnableD3D5CompatibilityMode(); + } + + void DxvkD3D8InterfaceBridge::EnableD3D6CompatibilityMode() { + m_interface->EnableD3D6CompatibilityMode(); + } + + void DxvkD3D8InterfaceBridge::EnableD3D7CompatibilityMode() { + m_interface->EnableD3D7CompatibilityMode(); + } + void DxvkD3D8InterfaceBridge::EnableD3D8CompatibilityMode() { m_interface->EnableD3D8CompatibilityMode(); } diff --git a/src/d3d9/d3d9_bridge.h b/src/d3d9/d3d9_bridge.h index e5c2f3e7d16..6eff01b3a06 100644 --- a/src/d3d9/d3d9_bridge.h +++ b/src/d3d9/d3d9_bridge.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "../util/config/config.h" /** @@ -20,6 +21,7 @@ IDxvkD3D8Bridge : public IUnknown { #ifdef DXVK_D3D9_NAMESPACE using IDirect3DSurface9 = d3d9::IDirect3DSurface9; using D3DFORMAT = d3d9::D3DFORMAT; + using D3DPRESENT_PARAMETERS = d3d9::D3DPRESENT_PARAMETERS; #endif /** @@ -30,6 +32,12 @@ IDxvkD3D8Bridge : public IUnknown { * \param [in] pSrcRect Source rectangle * \param [in] pDestPoint Destination (top-left) point */ + virtual uint32_t DetermineInitialTextureMemory() = 0; + virtual HRESULT ResetSwapChain(D3DPRESENT_PARAMETERS* Params) = 0; + virtual HRESULT SetColorKeyState(bool colorKeyState) = 0; + virtual HRESULT SetColorKey(DWORD colorKeyLow, DWORD colorKeyHigh) = 0; + virtual HRESULT SetLegacyLightsState(bool legacyLightsState, bool isD3DLight2) = 0; + virtual HRESULT UpdateTextureFromBuffer( IDirect3DSurface9* pDestSurface, IDirect3DSurface9* pSrcSurface, @@ -52,6 +60,10 @@ IDxvkD3D8InterfaceBridge : public IUnknown { /** * \brief Enforces D3D8-specific features and validations */ + virtual void EnableD3D3CompatibilityMode() = 0; + virtual void EnableD3D5CompatibilityMode() = 0; + virtual void EnableD3D6CompatibilityMode() = 0; + virtual void EnableD3D7CompatibilityMode() = 0; virtual void EnableD3D8CompatibilityMode() = 0; /** @@ -86,6 +98,12 @@ namespace dxvk { REFIID riid, void** ppvObject); + uint32_t DetermineInitialTextureMemory(); + HRESULT ResetSwapChain(D3DPRESENT_PARAMETERS* Params); + HRESULT SetColorKeyState(bool colorKeyState); + HRESULT SetColorKey(DWORD colorKeyLow, DWORD colorKeyHigh); + HRESULT SetLegacyLightsState(bool legacyLightsState, bool isD3DLight2); + HRESULT UpdateTextureFromBuffer( IDirect3DSurface9* pDestSurface, IDirect3DSurface9* pSrcSurface, @@ -114,6 +132,10 @@ namespace dxvk { REFIID riid, void** ppvObject); + void EnableD3D3CompatibilityMode(); + void EnableD3D5CompatibilityMode(); + void EnableD3D6CompatibilityMode(); + void EnableD3D7CompatibilityMode(); void EnableD3D8CompatibilityMode(); const Config* GetConfig() const; diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index a6b2b4d1a55..e15ad203b5c 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -150,8 +150,9 @@ namespace dxvk { return D3DERR_INVALIDCALL; // RENDERTARGET and DEPTHSTENCIL must be default pool - constexpr DWORD incompatibleUsages = D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL; - if (pDesc->Pool != D3DPOOL_DEFAULT && (pDesc->Usage & incompatibleUsages)) + constexpr DWORD usageRTOrDS = D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL; + + if (pDesc->Pool != D3DPOOL_DEFAULT && (pDesc->Usage & usageRTOrDS)) return D3DERR_INVALIDCALL; // Use the maximum possible mip level count if the supplied diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index efa8d4ff025..61aa365b863 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -49,6 +49,10 @@ namespace dxvk { , m_d3d9Options ( dxvkDevice, pParent->GetInstance()->config() ) , m_multithread ( BehaviorFlags & D3DCREATE_MULTITHREADED ) , m_isSWVP ( (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) ? true : false ) + , m_isD3D3Compatible ( pParent->IsD3D3Compatible() ) + , m_isD3D5Compatible ( pParent->IsD3D5Compatible() ) + , m_isD3D6Compatible ( pParent->IsD3D6Compatible() ) + , m_isD3D7Compatible ( pParent->IsD3D7Compatible() ) , m_isD3D8Compatible ( pParent->IsD3D8Compatible() ) , m_csThread ( dxvkDevice, dxvkDevice->createContext() ) , m_csChunk ( AllocCsChunk() ) @@ -7429,7 +7433,7 @@ namespace dxvk { m_flags.set(D3D9DeviceFlag::DirtyFFVertexShader); // PS - rs[D3DRS_SPECULARENABLE] = FALSE; + rs[D3DRS_SPECULARENABLE] = m_isD3D5Compatible ? TRUE : FALSE; rs[D3DRS_AMBIENT] = 0; m_flags.set(D3D9DeviceFlag::DirtyFFVertexData); @@ -7437,8 +7441,8 @@ namespace dxvk { rs[D3DRS_FOGENABLE] = FALSE; rs[D3DRS_FOGCOLOR] = 0; rs[D3DRS_FOGTABLEMODE] = D3DFOG_NONE; - rs[D3DRS_FOGSTART] = bit::cast(0.0f); - rs[D3DRS_FOGEND] = bit::cast(1.0f); + rs[D3DRS_FOGSTART] = m_isD3D6Compatible ? bit::cast(1.0f) : bit::cast(0.0f); + rs[D3DRS_FOGEND] = m_isD3D6Compatible ? bit::cast(100.0f) : bit::cast(1.0f); rs[D3DRS_FOGDENSITY] = bit::cast(1.0f); rs[D3DRS_FOGVERTEXMODE] = D3DFOG_NONE; m_flags.set(D3D9DeviceFlag::DirtyFogColor); @@ -7485,7 +7489,7 @@ namespace dxvk { rs[D3DRS_WRAP6] = 0; rs[D3DRS_WRAP7] = 0; rs[D3DRS_CLIPPING] = TRUE; - rs[D3DRS_MULTISAMPLEANTIALIAS] = TRUE; + rs[D3DRS_MULTISAMPLEANTIALIAS] = m_isD3D7Compatible ? FALSE : TRUE; rs[D3DRS_PATCHEDGESTYLE] = D3DPATCHEDGE_DISCRETE; rs[D3DRS_DEBUGMONITORTOKEN] = D3DDMT_ENABLE; rs[D3DRS_POSITIONDEGREE] = D3DDEGREE_CUBIC; diff --git a/src/d3d9/d3d9_device.h b/src/d3d9/d3d9_device.h index f65a2f86d18..f0e58b3d185 100644 --- a/src/d3d9/d3d9_device.h +++ b/src/d3d9/d3d9_device.h @@ -928,6 +928,29 @@ namespace dxvk { const D3D9ConstantLayout& GetPixelConstantLayout() { return m_psLayout; } HRESULT ResetState(D3DPRESENT_PARAMETERS* pPresentationParameters); + HRESULT SetColorKeyState(bool colorKeyState) { + if (likely(m_colorKeyEnabled != colorKeyState)) { + m_colorKeyEnabled = colorKeyState; + } + return D3D_OK; + } + + HRESULT SetLegacyLightsState(bool legacyLightState, bool isD3DLight2) { + if (likely(m_useLegacyLights != legacyLightState)) { + m_useLegacyLights = legacyLightState; + m_isD3DLight2 = isD3DLight2; + } + return D3D_OK; + } + + HRESULT SetColorKey(DWORD colorKeyLow, DWORD colorKeyHigh) { + if (likely(m_state.colorKeyLow != colorKeyLow || m_state.colorKeyHigh != colorKeyHigh)) { + m_state.colorKeyLow = colorKeyLow; + m_state.colorKeyHigh = colorKeyHigh; + } + return D3D_OK; + } + HRESULT ResetSwapChain(D3DPRESENT_PARAMETERS* pPresentationParameters, D3DDISPLAYMODEEX* pFullscreenDisplayMode); HRESULT InitialReset(D3DPRESENT_PARAMETERS* pPresentationParameters, D3DDISPLAYMODEEX* pFullscreenDisplayMode); @@ -936,6 +959,22 @@ namespace dxvk { return m_samplerCount.load(); } + bool IsD3D3Compatible() const { + return m_isD3D3Compatible; + } + + bool IsD3D5Compatible() const { + return m_isD3D5Compatible; + } + + bool IsD3D6Compatible() const { + return m_isD3D6Compatible; + } + + bool IsD3D7Compatible() const { + return m_isD3D7Compatible; + } + bool IsD3D8Compatible() const { return m_isD3D8Compatible; } @@ -1274,6 +1313,16 @@ namespace dxvk { D3D9ShaderMasks m_psShaderMasks = FixedFunctionMask; bool m_isSWVP; + bool m_isD3D3Compatible; + bool m_isD3D5Compatible; + bool m_isD3D6Compatible; + bool m_isD3D7Compatible; + + // D3D7 and earlier color key transparency state + bool m_colorKeyEnabled = false; + // D3D6 and earlier legacy light model state + bool m_useLegacyLights = false; + bool m_isD3DLight2 = false; bool m_isD3D8Compatible; bool m_amdATOC = false; bool m_nvATOC = false; diff --git a/src/d3d9/d3d9_format.cpp b/src/d3d9/d3d9_format.cpp index fd3d11b53dd..548d1d0e79f 100644 --- a/src/d3d9/d3d9_format.cpp +++ b/src/d3d9/d3d9_format.cpp @@ -504,7 +504,7 @@ namespace dxvk { if (Format == D3D9Format::D32 && !m_d32supportFinal) return D3D9_VK_FORMAT_MAPPING(); - + if (!m_d24s8Support && mapping.FormatColor == VK_FORMAT_D24_UNORM_S8_UINT) mapping.FormatColor = VK_FORMAT_D32_SFLOAT_S8_UINT; @@ -577,16 +577,21 @@ namespace dxvk { return &unknown; } } - + + + void D3D9VkFormatTable::RefreshFormatSupport(bool isD3D8Compatible) { + m_w11v11u10Support = isD3D8Compatible; + } + bool D3D9VkFormatTable::CheckImageFormatSupport( const Rc& Adapter, VkFormat Format, VkFormatFeatureFlags Features) const { VkFormatProperties supported = Adapter->formatProperties(Format); - + return (supported.linearTilingFeatures & Features) == Features || (supported.optimalTilingFeatures & Features) == Features; } -} \ No newline at end of file +} diff --git a/src/d3d9/d3d9_format.h b/src/d3d9/d3d9_format.h index d5528ef721c..6005451ea45 100644 --- a/src/d3d9/d3d9_format.h +++ b/src/d3d9/d3d9_format.h @@ -212,6 +212,8 @@ namespace dxvk { const DxvkFormatInfo* GetUnsupportedFormatInfo( D3D9Format Format) const; + void RefreshFormatSupport(bool isD3D8Compatible); + private: bool CheckImageFormatSupport( @@ -225,6 +227,7 @@ namespace dxvk { bool m_dfSupport; bool m_x4r4g4b4Support; + bool m_w11v11u10Support; bool m_d32supportFinal; }; diff --git a/src/d3d9/d3d9_interface.h b/src/d3d9/d3d9_interface.h index 05533039bce..bdad386e6e8 100644 --- a/src/d3d9/d3d9_interface.h +++ b/src/d3d9/d3d9_interface.h @@ -128,10 +128,66 @@ namespace dxvk { bool IsExtended() { return m_extended; } + bool IsD3D3Compatible() const { + return m_isD3D3Compatible; + } + + bool IsD3D5Compatible() const { + return m_isD3D5Compatible; + } + + bool IsD3D6Compatible() const { + return m_isD3D6Compatible; + } + + bool IsD3D7Compatible() const { + return m_isD3D7Compatible; + } + bool IsD3D8Compatible() const { return m_isD3D8Compatible; } + void EnableD3D3CompatibilityMode() { + m_isD3D3Compatible = true; + m_isD3D5Compatible = true; + m_isD3D6Compatible = true; + m_isD3D7Compatible = true; + m_isD3D8Compatible = true; + RefreshAdapterFormatTables(); + Logger::info("The D3D9 interface is now operating in D3D3 compatibility mode."); + } + + void EnableD3D5CompatibilityMode() { + m_isD3D3Compatible = false; + m_isD3D5Compatible = true; + m_isD3D6Compatible = true; + m_isD3D7Compatible = true; + m_isD3D8Compatible = true; + RefreshAdapterFormatTables(); + Logger::info("The D3D9 interface is now operating in D3D5 compatibility mode."); + } + + void EnableD3D6CompatibilityMode() { + m_isD3D3Compatible = false; + m_isD3D5Compatible = false; + m_isD3D6Compatible = true; + m_isD3D7Compatible = true; + m_isD3D8Compatible = true; + RefreshAdapterFormatTables(); + Logger::info("The D3D9 interface is now operating in D3D6 compatibility mode."); + } + + void EnableD3D7CompatibilityMode() { + m_isD3D3Compatible = false; + m_isD3D5Compatible = false; + m_isD3D6Compatible = false; + m_isD3D7Compatible = true; + m_isD3D8Compatible = true; + RefreshAdapterFormatTables(); + Logger::info("The D3D9 interface is now operating in D3D7 compatibility mode."); + } + void EnableD3D8CompatibilityMode() { m_isD3D8Compatible = true; Logger::info("The D3D9 interface is now operating in D3D8 compatibility mode."); @@ -151,12 +207,21 @@ namespace dxvk { bool m_extended; + bool m_isD3D3Compatible = false; + bool m_isD3D5Compatible = false; + bool m_isD3D6Compatible = false; + bool m_isD3D7Compatible = false; bool m_isD3D8Compatible = false; + inline void RefreshAdapterFormatTables() { + for (auto& adapter : m_adapters) + adapter.RefreshFormatsTable(IsD3D8Compatible()); + } + D3D9Options m_d3d9Options; std::vector m_adapters; }; -} \ No newline at end of file +} diff --git a/src/d3d9/d3d9_options.cpp b/src/d3d9/d3d9_options.cpp index 6fecd8f75d6..2bdc4c2facf 100644 --- a/src/d3d9/d3d9_options.cpp +++ b/src/d3d9/d3d9_options.cpp @@ -72,6 +72,9 @@ namespace dxvk { this->alphaTestWiggleRoom = config.getOption ("d3d9.alphaTestWiggleRoom", false); this->cachedDynamicBuffers = config.getOption ("d3d9.cachedDynamicBuffers", false); this->deviceLocalConstantBuffers = config.getOption ("d3d9.deviceLocalConstantBuffers", false); + // D3D7/6/5/DDraw options + this->colorKeyCompatibility = config.getOption ("ddraw.colorKeyCompatibility", false); + this->allowDirectBufferMapping = config.getOption ("d3d9.allowDirectBufferMapping", true); this->seamlessCubes = config.getOption ("d3d9.seamlessCubes", false); diff --git a/src/d3d9/d3d9_options.h b/src/d3d9/d3d9_options.h index 737e1822c31..c14e1a9f44e 100644 --- a/src/d3d9/d3d9_options.h +++ b/src/d3d9/d3d9_options.h @@ -74,6 +74,11 @@ namespace dxvk { /// for rendering hazards bool generalHazards; + /// Color key compatibility mode for DDraw + /// + /// Circumvents the texelFetch color key shader path. + bool colorKeyCompatibility; + /// Anisotropic filter override /// /// Enforces anisotropic filtering with the diff --git a/src/d3d9/d3d9_state.h b/src/d3d9/d3d9_state.h index 5bec32bf9b7..3d85862fc4f 100644 --- a/src/d3d9/d3d9_state.h +++ b/src/d3d9/d3d9_state.h @@ -25,7 +25,7 @@ namespace dxvk { static constexpr DWORD AlphaToCoverageDisabled = MAKEFOURCC('A', '2', 'M', '0'); static constexpr DWORD AlphaToCoverageEnabled = MAKEFOURCC('A', '2', 'M', '1'); } - + struct D3D9ClipPlane { float coeff[4] = {}; }; @@ -159,7 +159,7 @@ namespace dxvk { float Padding[2]; } Stages[8]; }; - + struct D3D9VBO { Com vertexBuffer; @@ -229,6 +229,9 @@ namespace dxvk { float nPatchSegments = 0.0f; + DWORD colorKeyLow = 0; + DWORD colorKeyHigh = 0; + bool IsLightEnabled(DWORD Index) { const auto& indices = enabledLightIndices; return std::find(indices.begin(), indices.end(), Index) != indices.end(); @@ -299,4 +302,4 @@ namespace dxvk { uint32_t streamsUsed = 0; }; -} \ No newline at end of file +} diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index fc715cb80e7..dc08d6eee72 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -740,7 +740,8 @@ namespace dxvk { && cp[i].b == identity; } - if (!isIdentity && !m_presentParams.Windowed) + // For D3D7 and earlier we always fake windowed mode because of DDraw interop + if (!isIdentity && (!m_presentParams.Windowed || m_parent->IsD3D7Compatible())) m_blitter->setGammaRamp(NumControlPoints, cp.data()); else m_blitter->setGammaRamp(0, nullptr); @@ -1378,7 +1379,11 @@ namespace dxvk { std::string D3D9SwapChainEx::GetApiName() { - return this->GetParent()->IsD3D8Compatible() ? "D3D8" : + return this->GetParent()->IsD3D3Compatible() ? "D3D3" : + this->GetParent()->IsD3D5Compatible() ? "D3D5" : + this->GetParent()->IsD3D6Compatible() ? "D3D6" : + this->GetParent()->IsD3D7Compatible() ? "D3D7" : + this->GetParent()->IsD3D8Compatible() ? "D3D8" : this->GetParent()->IsExtended() ? "D3D9Ex" : "D3D9"; } diff --git a/src/d3d9/d3d9_util.cpp b/src/d3d9/d3d9_util.cpp index b5d24f44c09..16c31f3bca7 100644 --- a/src/d3d9/d3d9_util.cpp +++ b/src/d3d9/d3d9_util.cpp @@ -3,17 +3,17 @@ namespace dxvk { typedef HRESULT (STDMETHODCALLTYPE *D3DXDisassembleShader) ( - const void* pShader, - BOOL EnableColorCode, - char* pComments, + const void* pShader, + BOOL EnableColorCode, + char* pComments, ID3DBlob** ppDisassembly); // ppDisassembly is actually a D3DXBUFFER, but it has the exact same vtable as a ID3DBlob at the start. D3DXDisassembleShader g_pfnDisassembleShader = nullptr; HRESULT DisassembleShader( - const void* pShader, - BOOL EnableColorCode, - char* pComments, + const void* pShader, + BOOL EnableColorCode, + char* pComments, ID3DBlob** ppDisassembly) { if (g_pfnDisassembleShader == nullptr) { HMODULE d3d9x = LoadLibraryA("d3dx9.dll"); @@ -21,7 +21,7 @@ namespace dxvk { if (d3d9x == nullptr) d3d9x = LoadLibraryA("d3dx9_43.dll"); - g_pfnDisassembleShader = + g_pfnDisassembleShader = reinterpret_cast(GetProcAddress(d3d9x, "D3DXDisassembleShader")); } @@ -402,20 +402,6 @@ namespace dxvk { } - bool IsDepthFormat(D3D9Format Format) { - return Format == D3D9Format::D16_LOCKABLE - || Format == D3D9Format::D32 - || Format == D3D9Format::D15S1 - || Format == D3D9Format::D24S8 - || Format == D3D9Format::D24X8 - || Format == D3D9Format::D24X4S4 - || Format == D3D9Format::D16 - || Format == D3D9Format::D32F_LOCKABLE - || Format == D3D9Format::D24FS8 - || Format == D3D9Format::D32_LOCKABLE - || Format == D3D9Format::DF16 - || Format == D3D9Format::DF24 - || Format == D3D9Format::INTZ; - } + // IsDepthFormat moved inline to d3d9_util.h -} \ No newline at end of file +} diff --git a/src/d3d9/d3d9_util.h b/src/d3d9/d3d9_util.h index aea73e6d2d1..fccc3565b60 100644 --- a/src/d3d9/d3d9_util.h +++ b/src/d3d9/d3d9_util.h @@ -89,9 +89,9 @@ namespace dxvk { } HRESULT DisassembleShader( - const void* pShader, - BOOL EnableColorCode, - char* pComments, + const void* pShader, + BOOL EnableColorCode, + char* pComments, ID3DBlob** ppDisassembly); HRESULT DecodeMultiSampleType( @@ -202,7 +202,43 @@ namespace dxvk { return count; } - bool IsDepthFormat(D3D9Format Format); + inline bool IsDepthFormat(D3D9Format Format) { + return Format == D3D9Format::D16_LOCKABLE + || Format == D3D9Format::D32 + || Format == D3D9Format::D15S1 + || Format == D3D9Format::D24S8 + || Format == D3D9Format::D24X8 + || Format == D3D9Format::D24X4S4 + || Format == D3D9Format::D16 + || Format == D3D9Format::D32F_LOCKABLE + || Format == D3D9Format::D24FS8 + || Format == D3D9Format::D32_LOCKABLE + || Format == D3D9Format::DF16 + || Format == D3D9Format::DF24 + || Format == D3D9Format::INTZ; + } + + inline bool IsDepthStencilFormat(D3D9Format Format) { + return IsDepthFormat(Format) || Format == D3D9Format::S8_LOCKABLE; + } + + inline bool IsFourCCFormat(D3D9Format format) { + return format > D3D9Format::BINARYBUFFER; + } + + inline bool IsVendorFormat(D3D9Format format) { + return IsFourCCFormat(format) + && format != D3D9Format::MULTI2_ARGB8 + && format != D3D9Format::UYVY + && format != D3D9Format::R8G8_B8G8 + && format != D3D9Format::YUY2 + && format != D3D9Format::G8R8_G8B8 + && format != D3D9Format::DXT1 + && format != D3D9Format::DXT2 + && format != D3D9Format::DXT3 + && format != D3D9Format::DXT4 + && format != D3D9Format::DXT5; + } inline bool operator == (const D3DVIEWPORT9& a, const D3DVIEWPORT9& b) { return a.X == b.X && @@ -295,4 +331,4 @@ namespace dxvk { return D3D9TextureStageStateTypes(Type - 1); } -} \ No newline at end of file +} diff --git a/src/d3d9/shaders/d3d9_convert_yuy2_uyvy.comp b/src/d3d9/shaders/d3d9_convert_yuy2_uyvy.comp index 8f38c9bacf0..bbcf8e28381 100644 --- a/src/d3d9/shaders/d3d9_convert_yuy2_uyvy.comp +++ b/src/d3d9/shaders/d3d9_convert_yuy2_uyvy.comp @@ -3,7 +3,7 @@ #include "d3d9_convert_common.h" -layout(constant_id = 1225) const bool s_is_uyvy = false; +layout(constant_id = 0) const bool s_is_uyvy = false; layout( local_size_x = 8, diff --git a/src/d3d9/shaders/d3d9_fixed_function_common.glsl b/src/d3d9/shaders/d3d9_fixed_function_common.glsl new file mode 100644 index 00000000000..2ac989849be --- /dev/null +++ b/src/d3d9/shaders/d3d9_fixed_function_common.glsl @@ -0,0 +1,365 @@ +const float FloatMaxValue = 340282346638528859811704183484516925440.0; + +const uint TextureStageCount = 8; + +#define D3DFOGMODE uint +const uint D3DFOG_NONE = 0; +const uint D3DFOG_EXP = 1; +const uint D3DFOG_EXP2 = 2; +const uint D3DFOG_LINEAR = 3; + +struct D3D9RenderStateInfo { + float fogColor[3]; + float fogScale; + float fogEnd; + float fogDensity; + + uint alphaRef; + + float pointSize; + float pointSizeMin; + float pointSizeMax; + float pointScaleA; + float pointScaleB; + float pointScaleC; +}; + + +// Thanks SPIRV-Cross +spirv_instruction(set = "GLSL.std.450", id = 79) float spvNMin(float, float); +spirv_instruction(set = "GLSL.std.450", id = 79) vec2 spvNMin(vec2, vec2); +spirv_instruction(set = "GLSL.std.450", id = 79) vec3 spvNMin(vec3, vec3); +spirv_instruction(set = "GLSL.std.450", id = 79) vec4 spvNMin(vec4, vec4); +spirv_instruction(set = "GLSL.std.450", id = 81) float spvNClamp(float, float, float); +spirv_instruction(set = "GLSL.std.450", id = 81) vec2 spvNClamp(vec2, vec2, vec2); +spirv_instruction(set = "GLSL.std.450", id = 81) vec3 spvNClamp(vec3, vec3, vec3); +spirv_instruction(set = "GLSL.std.450", id = 81) vec4 spvNClamp(vec4, vec4, vec4); + + +// Dynamic "spec constants" +// Binding has to match with getSpecConstantBufferSlot in dxso_util.h +layout(set = 0, binding = 31, scalar) uniform SpecConsts { + uint dynamicSpecConstDword[20]; +}; + +layout (constant_id = 0) const uint SpecConstDword0 = 0; +layout (constant_id = 1) const uint SpecConstDword1 = 0; +layout (constant_id = 2) const uint SpecConstDword2 = 0; +layout (constant_id = 3) const uint SpecConstDword3 = 0; +layout (constant_id = 4) const uint SpecConstDword4 = 0; +layout (constant_id = 5) const uint SpecConstDword5 = 0; +layout (constant_id = 6) const uint SpecConstDword6 = 0; +layout (constant_id = 7) const uint SpecConstDword7 = 0; +layout (constant_id = 8) const uint SpecConstDword8 = 0; +layout (constant_id = 9) const uint SpecConstDword9 = 0; +layout (constant_id = 10) const uint SpecConstDword10 = 0; +layout (constant_id = 11) const uint SpecConstDword11 = 0; +layout (constant_id = 12) const uint SpecConstDword12 = 0; +layout (constant_id = 13) const uint SpecConstDword13 = 0; +layout (constant_id = 14) const uint SpecConstDword14 = 0; +layout (constant_id = 15) const uint SpecConstDword15 = 0; +layout (constant_id = 16) const uint SpecConstDword16 = 0; +layout (constant_id = 17) const uint SpecConstDword17 = 0; +layout (constant_id = 18) const uint SpecConstDword18 = 0; +layout (constant_id = 19) const uint SpecConstDword19 = 0; +layout (constant_id = 20) const uint SpecConstDword20 = 0; + +const uint SpecSamplerType = 0; +const uint SpecSamplerDepthMode = 1; +const uint SpecAlphaCompareOp = 2; +const uint SpecSamplerProjected = 3; +const uint SpecSamplerNull = 4; +const uint SpecAlphaPrecisionBits = 5; +const uint SpecFogEnabled = 6; +const uint SpecVertexFogMode = 7; +const uint SpecPixelFogMode = 8; +const uint SpecVertexShaderBools = 9; +const uint SpecPixelShaderBools = 10; +const uint SpecSamplerFetch4 = 11; +const uint SpecFFLastActiveTextureStage = 12; +const uint SpecSamplerDrefClamp = 13; +const uint SpecClipPlaneCount = 14; +const uint SpecPointMode = 15; +const uint SpecDrefScaling = 16; +const uint SpecFFGlobalSpecularEnabled = 17; +const uint SpecFFTextureStage0ColorOp = 18; +const uint SpecFFTextureStage0ColorArg1 = 19; +const uint SpecFFTextureStage0ColorArg2 = 20; +const uint SpecFFTextureStage0AlphaOp = 21; +const uint SpecFFTextureStage0AlphaArg1 = 22; +const uint SpecFFTextureStage0AlphaArg2 = 23; +const uint SpecFFTextureStage0ResultIsTemp = 24; +const uint SpecFFTextureStage1ColorOp = 25; +const uint SpecFFTextureStage1ColorArg1 = 26; +const uint SpecFFTextureStage1ColorArg2 = 27; +const uint SpecFFTextureStage1AlphaOp = 28; +const uint SpecFFTextureStage1AlphaArg1 = 29; +const uint SpecFFTextureStage1AlphaArg2 = 30; +const uint SpecFFTextureStage1ResultIsTemp = 31; +const uint SpecFFTextureStage2ColorOp = 32; +const uint SpecFFTextureStage2ColorArg1 = 33; +const uint SpecFFTextureStage2ColorArg2 = 34; +const uint SpecFFTextureStage2AlphaOp = 35; +const uint SpecFFTextureStage2AlphaArg1 = 36; +const uint SpecFFTextureStage2AlphaArg2 = 37; +const uint SpecFFTextureStage2ResultIsTemp = 38; +const uint SpecFFTextureStage3ColorOp = 39; +const uint SpecFFTextureStage3ColorArg1 = 40; +const uint SpecFFTextureStage3ColorArg2 = 41; +const uint SpecFFTextureStage3AlphaOp = 42; +const uint SpecFFTextureStage3AlphaArg1 = 43; +const uint SpecFFTextureStage3AlphaArg2 = 44; +const uint SpecFFTextureStage3ResultIsTemp = 45; +const uint SpecFFTextureStage4ColorOp = 46; +const uint SpecFFTextureStage4ColorArg1 = 47; +const uint SpecFFTextureStage4ColorArg2 = 48; +const uint SpecFFTextureStage4AlphaOp = 49; +const uint SpecFFTextureStage4AlphaArg1 = 50; +const uint SpecFFTextureStage4AlphaArg2 = 51; +const uint SpecFFTextureStage4ResultIsTemp = 52; +const uint SpecFFTextureStage5ColorOp = 53; +const uint SpecFFTextureStage5ColorArg1 = 54; +const uint SpecFFTextureStage5ColorArg2 = 55; +const uint SpecFFTextureStage5AlphaOp = 56; +const uint SpecFFTextureStage5AlphaArg1 = 57; +const uint SpecFFTextureStage5AlphaArg2 = 58; +const uint SpecFFTextureStage5ResultIsTemp = 59; +const uint SpecFFTextureStage6ColorOp = 60; +const uint SpecFFTextureStage6ColorArg1 = 61; +const uint SpecFFTextureStage6ColorArg2 = 62; +const uint SpecFFTextureStage6AlphaOp = 63; +const uint SpecFFTextureStage6AlphaArg1 = 64; +const uint SpecFFTextureStage6AlphaArg2 = 65; +const uint SpecFFTextureStage6ResultIsTemp = 66; +const uint SpecFFTextureStage7ColorOp = 67; +const uint SpecFFTextureStage7ColorArg1 = 68; +const uint SpecFFTextureStage7ColorArg2 = 69; +const uint SpecFFTextureStage7AlphaOp = 70; +const uint SpecFFTextureStage7AlphaArg1 = 71; +const uint SpecFFTextureStage7AlphaArg2 = 72; +const uint SpecFFTextureStage7ResultIsTemp = 73; +const uint SpecFFTextureStage0ColorArg0 = 74; +const uint SpecFFTextureStage1ColorArg0 = 75; +const uint SpecFFTextureStage2ColorArg0 = 76; +const uint SpecFFTextureStage3ColorArg0 = 77; +const uint SpecFFTextureStage4ColorArg0 = 78; +const uint SpecFFTextureStage5ColorArg0 = 79; +const uint SpecFFTextureStage6ColorArg0 = 80; +const uint SpecFFTextureStage7ColorArg0 = 81; +const uint SpecFFTextureStage0AlphaArg0 = 82; +const uint SpecFFTextureStage1AlphaArg0 = 83; +const uint SpecFFTextureStage2AlphaArg0 = 84; +const uint SpecFFTextureStage3AlphaArg0 = 85; +const uint SpecFFTextureStage4AlphaArg0 = 86; +const uint SpecFFTextureStage5AlphaArg0 = 87; +const uint SpecFFTextureStage6AlphaArg0 = 88; +const uint SpecFFTextureStage7AlphaArg0 = 89; +const uint SpecFFColorKeyEnabled = 90; +const uint SpecFFColorKeyCompatibility = 91; +const uint SpecFFUseLegacyLights = 92; +const uint SpecFFIsLegacyD3DLight2 = 93; +const uint SpecFFColorKeyLow = 94; +const uint SpecFFColorKeyHigh = 95; +const uint SpecConstantCount = 96; + +struct BitfieldPosition { + uint dwordOffset; + uint bitOffset; + uint sizeInBits; +}; + +// Needs to match d3d9_spec_constants.h +BitfieldPosition SpecConstLayout[SpecConstantCount] = { + { 0, 0, 32 }, // SamplerType + + { 1, 0, 21 }, // SamplerDepthMode + { 1, 21, 3 }, // AlphaCompareOp + { 1, 24, 8 }, // SamplerProjected + + { 2, 0, 21 }, // SamplerNull + { 2, 21, 4 }, // AlphaPrecisionBits + { 2, 25, 1 }, // FogEnabled + { 2, 26, 2 }, // VertexFogMode + { 2, 28, 2 }, // PixelFogMode + + { 3, 0, 16 }, // VertexShaderBools + { 3, 16, 16 }, // PixelShaderBools + + { 4, 0, 16 }, // SamplerFetch4 + { 4, 16, 3 }, // FFLastActiveTextureStage + + { 5, 0, 21 }, // SamplerDrefClamp + { 5, 21, 3 }, // ClipPlaneCount + { 5, 24, 2 }, // PointMode + { 5, 26, 5 }, // DrefScaling + + { 6, 31, 1 }, // FFGlobalSpecularEnabled. + + { 6, 0, 5 }, // FFTextureStage0ColorOp + { 6, 5, 5 }, // FFTextureStage0ColorArg1 + { 6, 10, 5 }, // FFTextureStage0ColorArg2 + { 6, 15, 5 }, // FFTextureStage0AlphaOp + { 6, 20, 5 }, // FFTextureStage0AlphaArg1 + { 6, 25, 5 }, // FFTextureStage0AlphaArg2 + { 6, 30, 1 }, // FFTextureStage0ResultIsTemp + + { 7, 0, 5 }, // FFTextureStage1ColorOp + { 7, 5, 5 }, // FFTextureStage1ColorArg1 + { 7, 10, 5 }, // FFTextureStage1ColorArg2 + { 7, 15, 5 }, // FFTextureStage1AlphaOp + { 7, 20, 5 }, // FFTextureStage1AlphaArg1 + { 7, 25, 5 }, // FFTextureStage1AlphaArg2 + { 7, 30, 1 }, // FFTextureStage1ResultIsTemp + + { 8, 0, 5 }, // FFTextureStage2ColorOp + { 8, 5, 5 }, // FFTextureStage2ColorArg1 + { 8, 10, 5 }, // FFTextureStage2ColorArg2 + { 8, 15, 5 }, // FFTextureStage2AlphaOp + { 8, 20, 5 }, // FFTextureStage2AlphaArg1 + { 8, 25, 5 }, // FFTextureStage2AlphaArg2 + { 8, 30, 1 }, // FFTextureStage2ResultIsTemp + + { 9, 0, 5 }, // FFTextureStage3ColorOp + { 9, 5, 5 }, // FFTextureStage3ColorArg1 + { 9, 10, 5 }, // FFTextureStage3ColorArg2 + { 9, 15, 5 }, // FFTextureStage3AlphaOp + { 9, 20, 5 }, // FFTextureStage3AlphaArg1 + { 9, 25, 5 }, // FFTextureStage3AlphaArg2 + { 9, 30, 1 }, // FFTextureStage3ResultIsTemp + + { 10, 0, 5 }, // FFTextureStage4ColorOp + { 10, 5, 5 }, // FFTextureStage4ColorArg1 + { 10, 10, 5 }, // FFTextureStage4ColorArg2 + { 10, 15, 5 }, // FFTextureStage4AlphaOp + { 10, 20, 5 }, // FFTextureStage4AlphaArg1 + { 10, 25, 5 }, // FFTextureStage4AlphaArg2 + { 10, 30, 1 }, // FFTextureStage4ResultIsTemp + + { 11, 0, 5 }, // FFTextureStage5ColorOp + { 11, 5, 5 }, // FFTextureStage5ColorArg1 + { 11, 10, 5 }, // FFTextureStage5ColorArg2 + { 11, 15, 5 }, // FFTextureStage5AlphaOp + { 11, 20, 5 }, // FFTextureStage5AlphaArg1 + { 11, 25, 5 }, // FFTextureStage5AlphaArg2 + { 11, 30, 1 }, // FFTextureStage5ResultIsTemp + + { 12, 0, 5 }, // FFTextureStage6ColorOp + { 12, 5, 5 }, // FFTextureStage6ColorArg1 + { 12, 10, 5 }, // FFTextureStage6ColorArg2 + { 12, 15, 5 }, // FFTextureStage6AlphaOp + { 12, 20, 5 }, // FFTextureStage6AlphaArg1 + { 12, 25, 5 }, // FFTextureStage6AlphaArg2 + { 12, 30, 1 }, // FFTextureStage6ResultIsTemp + + { 13, 0, 5 }, // FFTextureStage7ColorOp + { 13, 5, 5 }, // FFTextureStage7ColorArg1 + { 13, 10, 5 }, // FFTextureStage7ColorArg2 + { 13, 15, 5 }, // FFTextureStage7AlphaOp + { 13, 20, 5 }, // FFTextureStage7AlphaArg1 + { 13, 25, 5 }, // FFTextureStage7AlphaArg2 + { 13, 30, 1 }, // FFTextureStage7ResultIsTemp + + { 14, 0, 5 }, // FFTextureStage0ColorArg0 + { 14, 5, 5 }, // FFTextureStage1ColorArg0 + { 14, 10, 5 }, // FFTextureStage2ColorArg0 + { 14, 15, 5 }, // FFTextureStage3ColorArg0 + { 14, 20, 5 }, // FFTextureStage4ColorArg0 + { 14, 25, 5 }, // FFTextureStage5ColorArg0 + + { 15, 0, 5 }, // FFTextureStage6ColorArg0 + { 15, 5, 5 }, // FFTextureStage7ColorArg0 + { 15, 10, 5 }, // FFTextureStage0AlphaArg0 + { 15, 15, 5 }, // FFTextureStage1AlphaArg0 + { 15, 20, 5 }, // FFTextureStage2AlphaArg0 + { 15, 25, 5 }, // FFTextureStage3AlphaArg0 + + { 16, 0, 5 }, // FFTextureStage4AlphaArg0 + { 16, 5, 5 }, // FFTextureStage5AlphaArg0 + { 16, 10, 5 }, // FFTextureStage6AlphaArg0 + { 16, 15, 5 }, // FFTextureStage7AlphaArg0 + { 16, 20, 1 }, // FFColorKeyEnable + { 16, 21, 1 }, // FFColorKeyCompatibility + { 16, 22, 1 }, // FFUseLegacyLights + { 16, 23, 1 }, // SpecFFIsLegacyD3DLight2 + + { 17, 0, 24 }, // FFColorKeyLow + + { 18, 0, 24 }, // FFColorKeyHigh +}; + +bool specIsOptimized() { + return SpecConstDword20 != 0u; +} + +uint specDword(uint index) { + if (!specIsOptimized()) { + return dynamicSpecConstDword[index]; + } + + switch (index) { + case 0u: + return SpecConstDword0; + case 1u: + return SpecConstDword1; + case 2u: + return SpecConstDword2; + case 3u: + return SpecConstDword3; + case 4u: + return SpecConstDword4; + case 5u: + return SpecConstDword5; + case 6u: + return SpecConstDword6; + case 7u: + return SpecConstDword7; + case 8u: + return SpecConstDword8; + case 9u: + return SpecConstDword9; + case 10u: + return SpecConstDword10; + case 11u: + return SpecConstDword11; + case 12u: + return SpecConstDword12; + case 13u: + return SpecConstDword13; + case 14u: + return SpecConstDword14; + case 15u: + return SpecConstDword15; + case 16u: + return SpecConstDword16; + case 17u: + return SpecConstDword17; + case 18u: + return SpecConstDword18; + case 19u: + return SpecConstDword19; + case 20u: + return SpecConstDword20; + default: + return 0u; + } +} + +uint specUint(uint specConstIdx, uint bitOffset, uint bits) { + BitfieldPosition pos = SpecConstLayout[specConstIdx]; + uint dword = specDword(pos.dwordOffset); + return bitfieldExtract(dword, int(pos.bitOffset + bitOffset), int(bits)); +} + +uint specUint(uint specConstIdx) { + BitfieldPosition pos = SpecConstLayout[specConstIdx]; + uint dword = specDword(pos.dwordOffset); + return bitfieldExtract(dword, int(pos.bitOffset), int(pos.sizeInBits)); +} + +bool specBool(uint specConstIdx, uint bitOffset) { + return specUint(specConstIdx, bitOffset, 1u) != 0u; +} + +bool specBool(uint specConstIdx) { + return specUint(specConstIdx) != 0u; +} diff --git a/src/d3d9/shaders/d3d9_fixed_function_frag.frag b/src/d3d9/shaders/d3d9_fixed_function_frag.frag new file mode 100644 index 00000000000..50f63c4a9fd --- /dev/null +++ b/src/d3d9/shaders/d3d9_fixed_function_frag.frag @@ -0,0 +1,4 @@ +#version 450 +#extension GL_GOOGLE_include_directive : enable + +#include "d3d9_fixed_function_frag.glsl" diff --git a/src/d3d9/shaders/d3d9_fixed_function_frag.glsl b/src/d3d9/shaders/d3d9_fixed_function_frag.glsl new file mode 100644 index 00000000000..f8baf9da2de --- /dev/null +++ b/src/d3d9/shaders/d3d9_fixed_function_frag.glsl @@ -0,0 +1,722 @@ +#extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_spirv_intrinsics : require +#extension GL_EXT_demote_to_helper_invocation : require +#extension GL_ARB_derivative_control : require +#extension GL_EXT_control_flow_attributes : require +#extension GL_EXT_nonuniform_qualifier : require + + +// The locations need to match with RegisterLinkerSlot in dxso_util.cpp +#ifndef INTERP_MODE +#define INTERP_MODE +#endif + +layout(location = 0) INTERP_MODE in vec4 in_Normal; +layout(location = 1) INTERP_MODE in vec4 in_Texcoord0; +layout(location = 2) INTERP_MODE in vec4 in_Texcoord1; +layout(location = 3) INTERP_MODE in vec4 in_Texcoord2; +layout(location = 4) INTERP_MODE in vec4 in_Texcoord3; +layout(location = 5) INTERP_MODE in vec4 in_Texcoord4; +layout(location = 6) INTERP_MODE in vec4 in_Texcoord5; +layout(location = 7) INTERP_MODE in vec4 in_Texcoord6; +layout(location = 8) INTERP_MODE in vec4 in_Texcoord7; +layout(location = 9) INTERP_MODE in vec4 in_Color0; +layout(location = 10) INTERP_MODE in vec4 in_Color1; +layout(location = 11) INTERP_MODE in float in_Fog; + +layout(location = 0) out vec4 out_Color0; + + +const uint TextureArgCount = 3; +const uint MaxSharedPushDataSize = 64; + +#include "d3d9_fixed_function_common.glsl" + +struct D3D9FFTextureStage { + uint Primitive[2]; +}; + +struct D3D9FixedFunctionPS { + vec4 textureFactor; + D3D9FFTextureStage Stages[8]; +}; + +struct D3D9SharedPSStage { + float Constant[4]; + float BumpEnvMat[2][2]; + float BumpEnvLScale; + float BumpEnvLOffset; + float Padding[2]; +}; + +struct D3D9SharedPS { + D3D9SharedPSStage Stages[TextureStageCount]; +}; + +const uint D3DTOP_DISABLE = 1; +const uint D3DTOP_SELECTARG1 = 2; +const uint D3DTOP_SELECTARG2 = 3; +const uint D3DTOP_MODULATE = 4; +const uint D3DTOP_MODULATE2X = 5; +const uint D3DTOP_MODULATE4X = 6; +const uint D3DTOP_ADD = 7; +const uint D3DTOP_ADDSIGNED = 8; +const uint D3DTOP_ADDSIGNED2X = 9; +const uint D3DTOP_SUBTRACT = 10; +const uint D3DTOP_ADDSMOOTH = 11; +const uint D3DTOP_BLENDDIFFUSEALPHA = 12; +const uint D3DTOP_BLENDTEXTUREALPHA = 13; +const uint D3DTOP_BLENDFACTORALPHA = 14; +const uint D3DTOP_BLENDTEXTUREALPHAPM = 15; +const uint D3DTOP_BLENDCURRENTALPHA = 16; +const uint D3DTOP_PREMODULATE = 17; +const uint D3DTOP_MODULATEALPHA_ADDCOLOR = 18; +const uint D3DTOP_MODULATECOLOR_ADDALPHA = 19; +const uint D3DTOP_MODULATEINVALPHA_ADDCOLOR = 20; +const uint D3DTOP_MODULATEINVCOLOR_ADDALPHA = 21; +const uint D3DTOP_BUMPENVMAP = 22; +const uint D3DTOP_BUMPENVMAPLUMINANCE = 23; +const uint D3DTOP_DOTPRODUCT3 = 24; +const uint D3DTOP_MULTIPLYADD = 25; +const uint D3DTOP_LERP = 26; + +const uint D3DTA_SELECTMASK = 0x0000000f; +const uint D3DTA_DIFFUSE = 0x00000000; +const uint D3DTA_CURRENT = 0x00000001; +const uint D3DTA_TEXTURE = 0x00000002; +const uint D3DTA_TFACTOR = 0x00000003; +const uint D3DTA_SPECULAR = 0x00000004; +const uint D3DTA_TEMP = 0x00000005; +const uint D3DTA_CONSTANT = 0x00000006; +const uint D3DTA_COMPLEMENT = 0x00000010; +const uint D3DTA_ALPHAREPLICATE = 0x00000020; + +const uint D3DRTYPE_SURFACE = 1; +const uint D3DRTYPE_VOLUME = 2; +const uint D3DRTYPE_TEXTURE = 3; +const uint D3DRTYPE_VOLUMETEXTURE = 4; +const uint D3DRTYPE_CUBETEXTURE = 5; +const uint D3DRTYPE_VERTEXBUFFER = 6; +const uint D3DRTYPE_INDEXBUFFER = 7; + +const uint VK_COMPARE_OP_NEVER = 0; +const uint VK_COMPARE_OP_LESS = 1; +const uint VK_COMPARE_OP_EQUAL = 2; +const uint VK_COMPARE_OP_LESS_OR_EQUAL = 3; +const uint VK_COMPARE_OP_GREATER = 4; +const uint VK_COMPARE_OP_NOT_EQUAL = 5; +const uint VK_COMPARE_OP_GREATER_OR_EQUAL = 6; +const uint VK_COMPARE_OP_ALWAYS = 7; + +const uint PerTextureStageSpecConsts = SpecFFTextureStage1ColorOp - SpecFFTextureStage0ColorOp; + + +// Bindings have to match with computeResourceSlotId in dxso_util.h +// computeResourceSlotId( +// DxsoProgramType::PixelShader, +// DxsoBindingType::ConstantBuffer, +// DxsoConstantBuffers::PSFixedFunction +// ) = 11 +layout(set = 0, binding = 11, scalar, row_major) uniform ShaderData { + D3D9FixedFunctionPS data; +}; + +// Bindings have to match with computeResourceSlotId in dxso_util.h +// computeResourceSlotId( +// DxsoProgramType::PixelShader, +// DxsoBindingType::ConstantBuffer, +// DxsoConstantBuffers::PSShared +// ) = 12 +layout(set = 0, binding = 12, scalar, row_major) uniform SharedData { + D3D9SharedPS sharedData; +}; + +layout(push_constant, scalar, row_major) uniform RenderStates { + D3D9RenderStateInfo rs; + + layout(offset = MaxSharedPushDataSize) uint packedSamplerIndices[TextureStageCount / 2]; +}; + +layout(set = 0, binding = 13) uniform texture2D t2d[TextureStageCount]; +layout(set = 0, binding = 13) uniform textureCube tcube[TextureStageCount]; +layout(set = 0, binding = 13) uniform texture3D t3d[TextureStageCount]; + +layout(set = 1, binding = 0) uniform sampler sampler_heap[]; + + +// Functions to extract information from the packed texture stages +uint colorOp(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 0, 5); +} +uint colorArg0(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 5, 6); +} +uint colorArg1(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 11, 6); +} +uint colorArg2(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 17, 6); +} + +uint alphaOp(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 23, 5); +} +uint alphaArg0(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[1], 0, 6); +} +uint alphaArg1(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[1], 6, 6); +} +uint alphaArg2(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[1], 12, 6); +} + +bool resultIsTemp(uint stageIndex) { + return bitfieldExtract(data.Stages[stageIndex].Primitive[1], 18, 1) != 0; +} + + +vec4 calculateFog(vec4 vPos, vec4 oColor) { + vec3 fogColor = vec3(rs.fogColor[0], rs.fogColor[1], rs.fogColor[2]); + float fogScale = rs.fogScale; + float fogEnd = rs.fogEnd; + float fogDensity = rs.fogDensity; + D3DFOGMODE fogMode = specUint(SpecPixelFogMode); + bool fogEnabled = specBool(SpecFogEnabled); + if (!fogEnabled) { + return oColor; + } + + float w = vPos.w; + float z = vPos.z; + float depth = z * (1.0 / w); + float fogFactor; + switch (fogMode) { + case D3DFOG_NONE: + fogFactor = in_Fog; + break; + + // (end - d) / (end - start) + case D3DFOG_LINEAR: + fogFactor = fogEnd - depth; + fogFactor = fogFactor * fogScale; + fogFactor = spvNClamp(fogFactor, 0.0, 1.0); + break; + + // 1 / (e^[d * density])^2 + case D3DFOG_EXP2: + // 1 / (e^[d * density]) + case D3DFOG_EXP: + fogFactor = depth * fogDensity; + + if (fogMode == D3DFOG_EXP2) + fogFactor *= fogFactor; + + // Provides the rcp. + fogFactor = -fogFactor; + fogFactor = exp(fogFactor); + break; + } + + vec4 color = oColor; + vec3 color3 = color.rgb; + vec3 fogFact3 = vec3(fogFactor); + vec3 lerpedFrog = mix(fogColor, color3, fogFact3); + return vec4(lerpedFrog.r, lerpedFrog.g, lerpedFrog.b, color.a); +} + + +// [D3D8] Scale Dref to [0..(2^N - 1)] for D24S8 and D16 if Dref scaling is enabled +float adjustDref(float reference, uint samplerIndex) { + uint drefScaleFactor = specUint(SpecDrefScaling); + if (drefScaleFactor != 0) { + float maxDref = 1.0 / (float(1 << drefScaleFactor) - 1.0); + reference *= maxDref; + } + if (specBool(SpecSamplerDrefClamp, samplerIndex)) { + reference = clamp(reference, 0.0, 1.0); + } + return reference; +} + + +vec4 calculateBumpmapCoords(uint stage, vec4 baseCoords, vec4 previousStageTextureVal) { + uint previousStage = stage - 1; + + vec4 coords = baseCoords; + [[unroll]] + for (uint i = 0; i < 2; i++) { + float tc_m_n = coords[i]; + vec2 bm = vec2(sharedData.Stages[previousStage].BumpEnvMat[i][0], sharedData.Stages[previousStage].BumpEnvMat[i][1]); + vec2 t = previousStageTextureVal.xy; + float result = tc_m_n + dot(bm, t); + coords[i] = result; + } + return coords; +} + + +uint loadSamplerHeapIndex(uint samplerBindingIndex) { + uint packedSamplerIndex = packedSamplerIndices[samplerBindingIndex / 2u]; + return bitfieldExtract(packedSamplerIndex, 16 * (int(samplerBindingIndex) & 1), 16); +} + + +vec4 sampleTexture(uint stage, vec4 texcoord, vec4 previousStageTextureVal) { + if (specBool(SpecSamplerProjected, stage)) { + texcoord /= texcoord.w; + } + + uint previousStageColorOp = 0; + if (stage > 0) { + previousStageColorOp = specIsOptimized() ? specUint(SpecFFTextureStage0ColorOp + PerTextureStageSpecConsts * (stage - 1)) : colorOp(stage - 1); + } + + if (stage != 0 && ( + previousStageColorOp == D3DTOP_BUMPENVMAP + || previousStageColorOp == D3DTOP_BUMPENVMAPLUMINANCE)) { + texcoord = calculateBumpmapCoords(stage, texcoord, previousStageTextureVal); + } + + vec4 texVal; + uint textureType = D3DRTYPE_TEXTURE + specUint(SpecSamplerType, 2u * stage, 2u); + switch (textureType) { + case D3DRTYPE_TEXTURE: + if (specBool(SpecSamplerDepthMode, stage)) { + texcoord.z = adjustDref(texcoord.z, stage); + texVal = texture(sampler2DShadow(t2d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord.xyz).xxxx; + } else { + if (!specBool(SpecFFColorKeyCompatibility) && specBool(SpecFFColorKeyEnabled)) { + const ivec2 texSize = textureSize(sampler2D(t2d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), 0); + const ivec2 pixelCoord = ivec2(texcoord.xy * vec2(texSize)); + texVal = texelFetch(sampler2D(t2d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), pixelCoord, 0); + const float ckrl = bitfieldExtract(specUint(SpecFFColorKeyLow), 0, 8); + const float ckgl = bitfieldExtract(specUint(SpecFFColorKeyLow), 8, 8); + const float ckbl = bitfieldExtract(specUint(SpecFFColorKeyLow), 16, 8); + const float ckrh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 0, 8); + const float ckgh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 8, 8); + const float ckbh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 16, 8); + const ivec3 src = ivec3(texVal.rgb * 255.0); + if (src.r >= ckrl && src.g >= ckgl && src.b >= ckbl && + src.r <= ckrh && src.g <= ckgh && src.b <= ckbh) { + discard; + } + } + + texVal = texture(sampler2D(t2d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord.xy); + } + break; + case D3DRTYPE_CUBETEXTURE: + if (specBool(SpecSamplerDepthMode, stage)) { + texcoord.w = adjustDref(texcoord.w, stage); + texVal = texture(samplerCubeShadow(tcube[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord).xxxx; + } else { + texVal = texture(samplerCube(tcube[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord.xyz); + } + break; + case D3DRTYPE_VOLUMETEXTURE: + texVal = texture(sampler3D(t3d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord.xyz); + break; + default: + // This should never happen unless there's a major bug in the API implementation. + // Produce a value that's obviously wrong to make it obvious when it somehow does happen. + texVal = vec4(999.9); + break; + } + + if (specBool(SpecFFColorKeyCompatibility) && specBool(SpecFFColorKeyEnabled)) { + const float ckrl = bitfieldExtract(specUint(SpecFFColorKeyLow), 0, 8); + const float ckgl = bitfieldExtract(specUint(SpecFFColorKeyLow), 8, 8); + const float ckbl = bitfieldExtract(specUint(SpecFFColorKeyLow), 16, 8); + const float ckrh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 0, 8); + const float ckgh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 8, 8); + const float ckbh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 16, 8); + const ivec3 src = ivec3(texVal.rgb * 255.0); + if (src.r >= ckrl && src.g >= ckgl && src.b >= ckbl && + src.r <= ckrh && src.g <= ckgh && src.b <= ckbh) { + discard; + } + } + + if (stage != 0 && previousStageColorOp == D3DTOP_BUMPENVMAPLUMINANCE) { + float lScale = sharedData.Stages[stage - 1].BumpEnvLScale; + float lOffset = sharedData.Stages[stage - 1].BumpEnvLOffset; + float scale = texVal.z; + scale *= lScale; + scale += lOffset; + scale = clamp(scale, 0.0, 1.0); + texVal *= scale; + } + + return texVal; +} + + +vec4 readArgValue(uint stage, uint arg, vec4 current, vec4 temp, vec4 textureVal) { + vec4 reg = vec4(1.0); + switch (arg & D3DTA_SELECTMASK) { + case D3DTA_CONSTANT: + reg = vec4( + sharedData.Stages[stage].Constant[0], + sharedData.Stages[stage].Constant[1], + sharedData.Stages[stage].Constant[2], + sharedData.Stages[stage].Constant[3] + ); + break; + case D3DTA_CURRENT: + reg = current; + break; + case D3DTA_DIFFUSE: + reg = in_Color0; + break; + case D3DTA_SPECULAR: + reg = in_Color1; + break; + case D3DTA_TEMP: + reg = temp; + break; + case D3DTA_TEXTURE: + reg = textureVal; + break; + case D3DTA_TFACTOR: + reg = data.textureFactor; + break; + } + + // reg = 1 - reg + if ((arg & D3DTA_COMPLEMENT) != 0) + reg = vec4(1.0) - reg; + + // reg = reg.wwww + if ((arg & D3DTA_ALPHAREPLICATE) != 0) + reg = reg.aaaa; + + return reg; +} + +struct TextureStageArguments { + uint arg0; + uint arg1; + uint arg2; +}; + +struct TextureStageArgumentValues { + vec4 arg0; + vec4 arg1; + vec4 arg2; +}; + +TextureStageArgumentValues readArgValues(uint stage, const TextureStageArguments args, vec4 current, vec4 temp, vec4 textureVal) { + TextureStageArgumentValues argVals; + argVals.arg0 = readArgValue(stage, args.arg0, current, temp, textureVal); + argVals.arg1 = readArgValue(stage, args.arg1, current, temp, textureVal); + argVals.arg2 = readArgValue(stage, args.arg2, current, temp, textureVal); + return argVals; +} + +uint repackArg(uint arg) { + // Move the flags by 1 bit. 0x18 = 0b11000 + return (arg & ~0x18) | ((arg & 0x18) << 1u); +} + +vec4 complement(vec4 val) { + return vec4(1.0) - val; +} + +vec4 saturate(vec4 val) { + return clamp(val, vec4(0.0), vec4(1.0)); +} + +vec4 calculateTextureStage(uint op, vec4 dst, const TextureStageArgumentValues arg, vec4 current, vec4 textureVal) { + switch (op) { + case D3DTOP_SELECTARG1: + return arg.arg1; + + case D3DTOP_SELECTARG2: + return arg.arg2; + + case D3DTOP_MODULATE4X: + return arg.arg1 * arg.arg2 * 4.0; + + case D3DTOP_MODULATE2X: + return arg.arg1 * arg.arg2 * 2.0; + + case D3DTOP_MODULATE: + return arg.arg1 * arg.arg2; + + case D3DTOP_ADDSIGNED2X: + return saturate(2.0 * (arg.arg1 + (arg.arg2 - vec4(0.5)))); + + case D3DTOP_ADDSIGNED: + return saturate(arg.arg1 + (arg.arg2 - vec4(0.5))); + + case D3DTOP_ADD: + return saturate(arg.arg1 + arg.arg2); + + case D3DTOP_SUBTRACT: + return saturate(arg.arg1 - arg.arg2); + + case D3DTOP_ADDSMOOTH: + return fma(complement(arg.arg1), arg.arg2, arg.arg1); + + case D3DTOP_BLENDDIFFUSEALPHA: + return mix(arg.arg2, arg.arg1, in_Color0.aaaa); + + case D3DTOP_BLENDTEXTUREALPHA: + return mix(arg.arg2, arg.arg1, textureVal.aaaa); + + case D3DTOP_BLENDFACTORALPHA: + return mix(arg.arg2, arg.arg1, data.textureFactor.aaaa); + + case D3DTOP_BLENDTEXTUREALPHAPM: + return saturate(fma(arg.arg2, complement(textureVal.aaaa), arg.arg1)); + + case D3DTOP_BLENDCURRENTALPHA: + return mix(arg.arg2, arg.arg1, current.aaaa); + + case D3DTOP_PREMODULATE: + return dst; // Not implemented + + case D3DTOP_MODULATEALPHA_ADDCOLOR: + return saturate(fma(arg.arg1.aaaa, arg.arg2, arg.arg1)); + + case D3DTOP_MODULATECOLOR_ADDALPHA: + return saturate(fma(arg.arg1, arg.arg2, arg.arg1.aaaa)); + + case D3DTOP_MODULATEINVALPHA_ADDCOLOR: + return saturate(fma(complement(arg.arg1.aaaa), arg.arg2, arg.arg1)); + + case D3DTOP_MODULATEINVCOLOR_ADDALPHA: + return saturate(fma(complement(arg.arg1), arg.arg2, arg.arg1.aaaa)); + + case D3DTOP_BUMPENVMAPLUMINANCE: + case D3DTOP_BUMPENVMAP: + // Load texture for the next stage... + return dst; + + case D3DTOP_DOTPRODUCT3: + return saturate(vec4(dot(arg.arg1.rgb - vec3(0.5), arg.arg2.rgb - vec3(0.5)) * 4.0)); + + case D3DTOP_MULTIPLYADD: + return saturate(fma(arg.arg1, arg.arg2, arg.arg0)); + + case D3DTOP_LERP: + return mix(arg.arg2, arg.arg1, arg.arg0); + + default: + // Unhandled texture op! + return dst; + + } + + return vec4(0.0); +} + + +void alphaTest() { + uint alphaFunc = specUint(SpecAlphaCompareOp); + uint alphaPrecision = specUint(SpecAlphaPrecisionBits); + uint alphaRefInitial = rs.alphaRef; + float alphaRef; + float alpha = out_Color0.a; + + if (alphaFunc == VK_COMPARE_OP_ALWAYS) { + return; + } + + // Check if the given bit precision is supported + bool useIntPrecision = alphaPrecision <= 8; + if (useIntPrecision) { + // Adjust alpha ref to the given range + uint alphaRefInt = (alphaRefInitial << alphaPrecision) | (alphaRefInitial >> (8 - alphaPrecision)); + + // Convert alpha ref to float since we'll do the comparison based on that + alphaRef = float(alphaRefInt); + + // Adjust alpha to the given range and round + float alphaFactor = float((256u << alphaPrecision) - 1u); + + alpha = round(alpha * alphaFactor); + } else { + alphaRef = float(alphaRefInitial) / 255.0; + } + + bool atestResult; + switch (alphaFunc) { + case VK_COMPARE_OP_NEVER: + atestResult = false; + break; + + case VK_COMPARE_OP_LESS: + atestResult = alpha < alphaRef; + break; + + case VK_COMPARE_OP_EQUAL: + atestResult = alpha == alphaRef; + break; + + case VK_COMPARE_OP_LESS_OR_EQUAL: + atestResult = alpha <= alphaRef; + break; + + case VK_COMPARE_OP_GREATER: + atestResult = alpha > alphaRef; + break; + + case VK_COMPARE_OP_NOT_EQUAL: + atestResult = alpha != alphaRef; + break; + + case VK_COMPARE_OP_GREATER_OR_EQUAL: + atestResult = alpha >= alphaRef; + break; + + default: + case VK_COMPARE_OP_ALWAYS: + atestResult = true; + break; + } + + bool atestDiscard = !atestResult; + if (atestDiscard) { + demote; + } +} + +struct TextureStageState { + vec4 current; + vec4 temp; + vec4 previousStageTextureVal; +}; + +TextureStageState runTextureStage(uint stage, TextureStageState state) { + if (stage > specUint(SpecFFLastActiveTextureStage)) { + return state; + } + + const uint colorOp = specIsOptimized() ? specUint(SpecFFTextureStage0ColorOp + PerTextureStageSpecConsts * stage) : colorOp(stage); + + // This cancels all subsequent stages. + if (colorOp == D3DTOP_DISABLE) + return state; + + const bool resultIsTemp = specIsOptimized() ? specBool(SpecFFTextureStage0ResultIsTemp + PerTextureStageSpecConsts * stage) : resultIsTemp(stage); + vec4 dst = resultIsTemp ? state.temp : state.current; + + const uint alphaOp = specIsOptimized() ? specUint(SpecFFTextureStage0AlphaOp + PerTextureStageSpecConsts * stage) : alphaOp(stage); + + const TextureStageArguments colorArgs = { + // Color arg0 and alpha arg0 for all stages are packed after all the other FF spec consts + specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0ColorArg0 + stage)) : colorArg0(stage), + specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0ColorArg1 + PerTextureStageSpecConsts * stage)) : colorArg1(stage), + specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0ColorArg2 + PerTextureStageSpecConsts * stage)) : colorArg2(stage) + }; + const TextureStageArguments alphaArgs = { + // Color arg0 and alpha arg0 for all stages are packed after all the other FF spec consts + specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0AlphaArg0 + stage)) : alphaArg0(stage), + specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0AlphaArg1 + PerTextureStageSpecConsts * stage)) : alphaArg1(stage), + specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0AlphaArg2 + PerTextureStageSpecConsts * stage)) : alphaArg2(stage) + }; + + vec4 textureVal = vec4(0.0); + bool usesTexture = (colorArgs.arg0 & D3DTA_SELECTMASK) == D3DTA_TEXTURE + || (colorArgs.arg1 & D3DTA_SELECTMASK) == D3DTA_TEXTURE + || (colorArgs.arg2 & D3DTA_SELECTMASK) == D3DTA_TEXTURE + || (alphaArgs.arg0 & D3DTA_SELECTMASK) == D3DTA_TEXTURE + || (alphaArgs.arg1 & D3DTA_SELECTMASK) == D3DTA_TEXTURE + || (alphaArgs.arg2 & D3DTA_SELECTMASK) == D3DTA_TEXTURE; + + if (usesTexture) { + // We need to replace TEXCOORD inputs with gl_PointCoord + // if D3DRS_POINTSPRITEENABLE is set. + const uint pointMode = specUint(SpecPointMode); + const bool isSprite = bitfieldExtract(pointMode, 1, 1) == 1u; + + vec4 texCoord; + if (isSprite) { + texCoord = vec4(gl_PointCoord, 0.0, 0.0); + } else { + switch (stage) { + case 0: texCoord = in_Texcoord0; break; + case 1: texCoord = in_Texcoord1; break; + case 2: texCoord = in_Texcoord2; break; + case 3: texCoord = in_Texcoord3; break; + case 4: texCoord = in_Texcoord4; break; + case 5: texCoord = in_Texcoord5; break; + case 6: texCoord = in_Texcoord6; break; + case 7: texCoord = in_Texcoord7; break; + } + } + const vec4 unboundTextureConst = vec4(0.0, 0.0, 0.0, 1.0); + textureVal = !specBool(SpecSamplerNull, stage) ? sampleTexture(stage, texCoord, state.previousStageTextureVal) : unboundTextureConst; + } + + // Fast path if alpha/color path is identical. + // D3DTOP_DOTPRODUCT3 also has special quirky behaviour here. + const bool fastPath = colorOp == alphaOp && colorArgs == alphaArgs; + if (fastPath || colorOp == D3DTOP_DOTPRODUCT3) { + TextureStageArgumentValues colorArgVals = readArgValues(stage, colorArgs, state.current, state.temp, textureVal); + dst = calculateTextureStage(colorOp, dst, colorArgVals, state.current, textureVal); + } else { + vec4 colorResult = dst; + vec4 alphaResult = dst; + + TextureStageArgumentValues colorArgVals = readArgValues(stage, colorArgs, state.current, state.temp, textureVal); + colorResult = calculateTextureStage(colorOp, dst, colorArgVals, state.current, textureVal); + + if (alphaOp != D3DTOP_DISABLE) { + TextureStageArgumentValues alphaArgVals = readArgValues(stage, alphaArgs, state.current, state.temp, textureVal); + alphaResult = calculateTextureStage(alphaOp, dst, alphaArgVals, state.current, textureVal); + } + + dst.xyz = colorResult.xyz; + + // src0.x, src0.y, src0.z src1.w + if (alphaOp != D3DTOP_DISABLE) { + dst.a = alphaResult.a; + } + } + + if (resultIsTemp) { + state.temp = dst; + } else { + state.current = dst; + } + state.previousStageTextureVal = textureVal; + + return state; +} + +void main() { + // in_Color0 is diffuse + // in_Color1 is specular + + TextureStageState state; + // Current starts of as equal to diffuse. + state.current = in_Color0; + // Temp starts off as equal to vec4(0) + state.temp = vec4(0.0); + state.previousStageTextureVal = vec4(0.0); + + // If we turn this into a loop, performance becomes very poor on the proprietary Nvidia driver + // because it fails to unroll it. + state = runTextureStage(0, state); + state = runTextureStage(1, state); + state = runTextureStage(2, state); + state = runTextureStage(3, state); + state = runTextureStage(4, state); + state = runTextureStage(5, state); + state = runTextureStage(6, state); + state = runTextureStage(7, state); + + if (specBool(SpecFFGlobalSpecularEnabled)) { + state.current.xyz += in_Color1.xyz; + } + + state.current = calculateFog(gl_FragCoord, state.current); + + out_Color0 = state.current; + + alphaTest(); +} diff --git a/src/d3d9/shaders/d3d9_fixed_function_frag_sample.frag b/src/d3d9/shaders/d3d9_fixed_function_frag_sample.frag new file mode 100644 index 00000000000..807f90b8749 --- /dev/null +++ b/src/d3d9/shaders/d3d9_fixed_function_frag_sample.frag @@ -0,0 +1,6 @@ +#version 450 +#extension GL_GOOGLE_include_directive : enable + +#define INTERP_MODE sample + +#include "d3d9_fixed_function_frag.glsl" diff --git a/src/d3d9/shaders/d3d9_fixed_function_vert.vert b/src/d3d9/shaders/d3d9_fixed_function_vert.vert new file mode 100644 index 00000000000..1504405a307 --- /dev/null +++ b/src/d3d9/shaders/d3d9_fixed_function_vert.vert @@ -0,0 +1,725 @@ +#version 450 +#extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_spirv_intrinsics : require + +layout(location = 0) in vec4 in_Position0; +layout(location = 1) in vec4 in_Normal0; +layout(location = 2) in vec4 in_Position1; +layout(location = 3) in vec4 in_Normal1; +layout(location = 4) in vec4 in_Texcoord0; +layout(location = 5) in vec4 in_Texcoord1; +layout(location = 6) in vec4 in_Texcoord2; +layout(location = 7) in vec4 in_Texcoord3; +layout(location = 8) in vec4 in_Texcoord4; +layout(location = 9) in vec4 in_Texcoord5; +layout(location = 10) in vec4 in_Texcoord6; +layout(location = 11) in vec4 in_Texcoord7; +layout(location = 12) in vec4 in_Color0; +layout(location = 13) in vec4 in_Color1; +layout(location = 14) in float in_Fog; +layout(location = 15) in float in_PointSize; +layout(location = 16) in vec4 in_BlendWeight; +layout(location = 17) in vec4 in_BlendIndices; + + +// The locations need to match with RegisterLinkerSlot in dxso_util.cpp +precise gl_Position; +const uint MaxClipPlaneCount = 6; +out float gl_ClipDistance[MaxClipPlaneCount]; +layout(location = 0) out vec4 out_Normal; +layout(location = 1) out vec4 out_Texcoord0; +layout(location = 2) out vec4 out_Texcoord1; +layout(location = 3) out vec4 out_Texcoord2; +layout(location = 4) out vec4 out_Texcoord3; +layout(location = 5) out vec4 out_Texcoord4; +layout(location = 6) out vec4 out_Texcoord5; +layout(location = 7) out vec4 out_Texcoord6; +layout(location = 8) out vec4 out_Texcoord7; +layout(location = 9) out vec4 out_Color0; +layout(location = 10) out vec4 out_Color1; +layout(location = 11) out float out_Fog; + + +#include "d3d9_fixed_function_common.glsl" + +const uint MaxEnabledLights = 8; + +struct D3D9ViewportInfo { + vec4 inverseOffset; + vec4 inverseExtent; +}; + +#define D3DLIGHTTYPE uint +const uint D3DLIGHT_POINT = 1; +const uint D3DLIGHT_SPOT = 2; +const uint D3DLIGHT_DIRECTIONAL = 3; + +struct D3D9Light { + vec4 Diffuse; + vec4 Specular; + vec4 Ambient; + + vec4 Position; + vec4 Direction; + + D3DLIGHTTYPE Type; + float Range; + float Falloff; + float Attenuation0; + float Attenuation1; + float Attenuation2; + float Theta; + float Phi; +}; + +#define D3DCOLORVALUE vec4 + +struct D3DMATERIAL9 { + D3DCOLORVALUE Diffuse; + D3DCOLORVALUE Ambient; + D3DCOLORVALUE Specular; + D3DCOLORVALUE Emissive; + float Power; +}; + +struct D3D9FixedFunctionVS { + mat4 WorldView; + mat4 NormalMatrix; + mat4 InverseView; + mat4 Projection; + + mat4 TexcoordMatrices[TextureStageCount]; + + D3D9ViewportInfo ViewportInfo; + + vec4 GlobalAmbient; + D3D9Light Lights[MaxEnabledLights]; + D3DMATERIAL9 Material; + float TweenFactor; + + uint KeyPrimitives[4]; +}; + +#define D3D9FF_VertexBlendMode uint +const uint D3D9FF_VertexBlendMode_Disabled = 0; +const uint D3D9FF_VertexBlendMode_Normal = 1; +const uint D3D9FF_VertexBlendMode_Tween = 2; + +#define D3DMATERIALCOLORSOURCE uint +const uint D3DMCS_MATERIAL = 0; +const uint D3DMCS_COLOR1 = 1; +const uint D3DMCS_COLOR2 = 2; + +#define D3DTEXTURETRANSFORMFLAGS uint +const uint D3DTTFF_DISABLE = 0; +const uint D3DTTFF_COUNT1 = 1; +const uint D3DTTFF_COUNT2 = 2; +const uint D3DTTFF_COUNT3 = 3; +const uint D3DTTFF_COUNT4 = 4; +const uint D3DTTFF_PROJECTED = 256; + +const uint DXVK_TSS_TCI_PASSTHRU = 0x00000000; +const uint DXVK_TSS_TCI_CAMERASPACENORMAL = 0x00010000; +const uint DXVK_TSS_TCI_CAMERASPACEPOSITION = 0x00020000; +const uint DXVK_TSS_TCI_CAMERASPACEREFLECTIONVECTOR = 0x00030000; +const uint DXVK_TSS_TCI_SPHEREMAP = 0x00040000; + +const uint TCIOffset = 16; +const uint TCIMask = (7 << TCIOffset); + + +// Bindings have to match with computeResourceSlotId in dxso_util.h +// computeResourceSlotId( +// DxsoProgramType::VertexShader, +// DxsoBindingType::ConstantBuffer, +// DxsoConstantBuffers::VSFixedFunction +// ) = 4 +layout(set = 0, binding = 4, scalar, row_major) uniform ShaderData { + D3D9FixedFunctionVS data; +}; + +layout(push_constant, scalar, row_major) uniform RenderStates { + D3D9RenderStateInfo rs; +}; + +// Bindings have to match with computeResourceSlotId in dxso_util.h +// computeResourceSlotId( +// DxsoProgramType::VertexShader, +// DxsoBindingType::ConstantBuffer, +// DxsoConstantBuffers::VSVertexBlendData +// ) = 5 +layout(set = 0, binding = 5, std140, row_major) readonly buffer VertexBlendData { + mat4 WorldViewArray[]; +}; + + +// Bindings have to match with computeResourceSlotId in dxso_util.h +// computeResourceSlotId( +// DxsoProgramType::VertexShader, +// DxsoBindingType::ConstantBuffer, +// DxsoConstantBuffers::VSClipPlanes +// ) = 3 +layout(set = 0, binding = 3, std140) uniform ClipPlanes { + vec4 clipPlanes[MaxClipPlaneCount]; +}; + + +// Functions to extract information from the packed VS key +// See D3D9FFShaderKeyVSData in d3d9_shader_types.h +// Please, dearest compiler, inline all of this. +uint texcoordIndices() { + return bitfieldExtract(data.KeyPrimitives[0], 0, 24); +} +bool vertexHasPositionT() { + return bitfieldExtract(data.KeyPrimitives[0], 24, 1) != 0; +} +bool vertexHasColor0() { + return bitfieldExtract(data.KeyPrimitives[0], 25, 1) != 0; +} +bool vertexHasColor1() { + return bitfieldExtract(data.KeyPrimitives[0], 26, 1) != 0; +} +bool vertexHasPointSize() { + return bitfieldExtract(data.KeyPrimitives[0], 27, 1) != 0; +} +bool useLighting() { + return bitfieldExtract(data.KeyPrimitives[0], 28, 1) != 0; +} +bool normalizeNormals() { + return bitfieldExtract(data.KeyPrimitives[0], 29, 1) != 0; +} +bool localViewer() { + return bitfieldExtract(data.KeyPrimitives[0], 30, 1) != 0; +} +bool rangeFog() { + return bitfieldExtract(data.KeyPrimitives[0], 31, 1) != 0; +} + +uint texcoordFlags() { + return bitfieldExtract(data.KeyPrimitives[1], 0, 24); +} +uint diffuseSource() { + return bitfieldExtract(data.KeyPrimitives[1], 24, 2); +} +uint ambientSource() { + return bitfieldExtract(data.KeyPrimitives[1], 26, 2); +} +uint specularSource() { + return bitfieldExtract(data.KeyPrimitives[1], 28, 2); +} +uint emissiveSource() { + return bitfieldExtract(data.KeyPrimitives[1], 30, 2); +} + +uint transformFlags() { + return bitfieldExtract(data.KeyPrimitives[2], 0, 24); +} +uint lightCount() { + return bitfieldExtract(data.KeyPrimitives[2], 24, 4); +} +bool specularEnabled() { + return bitfieldExtract(data.KeyPrimitives[2], 28, 1) != 0; +} + +uint vertexTexcoordDeclMask() { + return bitfieldExtract(data.KeyPrimitives[3], 0, 24); +} +bool vertexHasFog() { + return bitfieldExtract(data.KeyPrimitives[3], 24, 1) != 0; +} +D3D9FF_VertexBlendMode blendMode() { + return bitfieldExtract(data.KeyPrimitives[3], 25, 2); +} +bool vertexBlendIndexed() { + return bitfieldExtract(data.KeyPrimitives[3], 27, 1) != 0; +} +uint vertexBlendCount() { + return bitfieldExtract(data.KeyPrimitives[3], 28, 2); +} +bool vertexClipping() { + return bitfieldExtract(data.KeyPrimitives[3], 30, 1) != 0; +} + + +float calculateFog(vec4 vPos, vec4 oColor) { + vec4 specular = in_Color1; + bool hasSpecular = vertexHasColor1(); + + vec3 fogColor = vec3(rs.fogColor[0], rs.fogColor[1], rs.fogColor[2]); + float fogScale = rs.fogScale; + float fogEnd = rs.fogEnd; + float fogDensity = rs.fogDensity; + D3DFOGMODE fogMode = specUint(SpecVertexFogMode); + bool fogEnabled = specBool(SpecFogEnabled); + if (!fogEnabled) { + return 0.0; + } + + float w = vPos.w; + float z = vPos.z; + float depth; + if (rangeFog()) { + vec3 pos3 = vPos.xyz; + depth = length(pos3); + } else { + depth = vertexHasFog() ? in_Fog : abs(z); + } + float fogFactor; + if (vertexHasPositionT()) { + fogFactor = hasSpecular ? specular.w : 1.0; + } else { + switch (fogMode) { + case D3DFOG_NONE: + fogFactor = hasSpecular ? specular.w : 1.0; + break; + + // (end - d) / (end - start) + case D3DFOG_LINEAR: + fogFactor = fogEnd - depth; + fogFactor = fogFactor * fogScale; + fogFactor = spvNClamp(fogFactor, 0.0, 1.0); + break; + + // 1 / (e^[d * density])^2 + case D3DFOG_EXP2: + // 1 / (e^[d * density]) + case D3DFOG_EXP: + fogFactor = depth * fogDensity; + + if (fogMode == D3DFOG_EXP2) + fogFactor *= fogFactor; + + // Provides the rcp. + fogFactor = -fogFactor; + fogFactor = exp(fogFactor); + break; + } + } + + return fogFactor; +} + + +float calculatePointSize(vec4 vtx) { + float value = vertexHasPointSize() ? in_PointSize : rs.pointSize; + uint pointMode = specUint(SpecPointMode); + bool isScale = bitfieldExtract(pointMode, 0, 1) != 0; + float scaleC = rs.pointScaleC; + float scaleB = rs.pointScaleB; + float scaleA = rs.pointScaleA; + + vec3 vtx3 = vtx.xyz; + + float DeSqr = dot(vtx3, vtx3); + float De = sqrt(DeSqr); + float scaleValue = scaleC * DeSqr; + scaleValue = fma(scaleB, De, scaleValue); + scaleValue += scaleA; + scaleValue = sqrt(scaleValue); + scaleValue = value / scaleValue; + + value = isScale ? scaleValue : value; + + float pointSizeMin = rs.pointSizeMin; + float pointSizeMax = rs.pointSizeMax; + + return clamp(value, pointSizeMin, pointSizeMax); +} + + +void emitVsClipping(vec4 vtx) { + vec4 worldPos = data.InverseView * vtx; + + // Always consider clip planes enabled when doing GPL by forcing 6 for the quick value. + uint clipPlaneCount = specUint(SpecClipPlaneCount); + + // Compute clip distances + for (uint i = 0; i < MaxClipPlaneCount; i++) { + vec4 clipPlane = clipPlanes[i]; + float dist = dot(worldPos, clipPlane); + bool clipPlaneEnabled = i < clipPlaneCount; + float value = clipPlaneEnabled ? dist : 0.0; + gl_ClipDistance[i] = value; + } +} + + +vec4 pickMaterialSource(uint source, vec4 material) { + if (source == D3DMCS_COLOR1 && vertexHasColor0()) + return in_Color0; + else if (source == D3DMCS_COLOR2 && vertexHasColor1()) + return in_Color1; + else + return material; +} + + +void main() { + vec4 vtx = in_Position0; + gl_Position = in_Position0; + vec3 normal = in_Normal0.xyz; + + if (blendMode() == D3D9FF_VertexBlendMode_Tween) { + vec4 vtx1 = in_Position1; + vec3 normal1 = in_Normal1.xyz; + vtx = mix(vtx, vtx1, data.TweenFactor); + normal = mix(normal, normal1, data.TweenFactor); + } + + if (!vertexHasPositionT()) { + if (blendMode() == D3D9FF_VertexBlendMode_Normal) { + float blendWeightRemaining = 1.0; + vec4 vtxSum = vec4(0.0); + vec3 nrmSum = vec3(0.0); + + for (uint i = 0; i <= vertexBlendCount(); i++) { + uint arrayIndex; + if (vertexBlendIndexed()) { + arrayIndex = uint(round(in_BlendIndices[i])); + } else { + arrayIndex = i; + } + mat4 worldView = WorldViewArray[arrayIndex]; + + mat3 nrmMtx; + for (uint j = 0; j < 3; j++) { + nrmMtx[j] = worldView[j].xyz; + } + + vec4 vtxResult = vtx * worldView; + vec3 nrmResult = normal * nrmMtx; + + float weight; + if (i != vertexBlendCount()) { + weight = in_BlendWeight[i]; + blendWeightRemaining -= weight; + } else { + weight = blendWeightRemaining; + } + + vec4 weightVec4 = vec4(weight, weight, weight, weight); + + vtxSum = fma(vtxResult, weightVec4, vtxSum); + nrmSum = fma(nrmResult, weightVec4.xyz, nrmSum); + } + + vtx = vtxSum; + normal = nrmSum; + } else { + vtx = vtx * data.WorldView; + + mat3 nrmMtx = mat3(data.NormalMatrix); + + normal = nrmMtx * normal; + } + + // Some games rely on normals not being normal. + if (normalizeNormals()) { + bool isZeroNormal = all(equal(normal, vec3(0.0, 0.0, 0.0))); + normal = isZeroNormal ? normal : normalize(normal); + } + + gl_Position = vtx * data.Projection; + } else { + gl_Position *= data.ViewportInfo.inverseExtent; + gl_Position += data.ViewportInfo.inverseOffset; + + // We still need to account for perspective correction here... + + float w = gl_Position.w; + float rhw = w == 0.0 ? 1.0 : 1.0 / w; + gl_Position.xyz *= rhw; + gl_Position.w = rhw; + } + + vec4 outNrm = vec4(normal, 1.0); + out_Normal = outNrm; + + vec4 texCoords[TextureStageCount]; + texCoords[0] = in_Texcoord0; + texCoords[1] = in_Texcoord1; + texCoords[2] = in_Texcoord2; + texCoords[3] = in_Texcoord3; + texCoords[4] = in_Texcoord4; + texCoords[5] = in_Texcoord5; + texCoords[6] = in_Texcoord6; + texCoords[7] = in_Texcoord7; + + vec4 transformedTexCoords[TextureStageCount]; + + for (uint i = 0; i < TextureStageCount; i++) { + // 0b111 = 7 + uint inputIndex = (texcoordIndices() >> (i * 3)) & 7; + uint inputFlags = (texcoordFlags() >> (i * 3)) & 7; + uint texcoordCount = (vertexTexcoordDeclMask() >> (inputIndex * 3)) & 7; + + vec4 transformed; + + uint flags = (transformFlags() >> (i * 3)) & 7; + + // Passing 0xffffffff results in it getting clamped to the dimensions of the texture coords and getting treated as PROJECTED + // but D3D9 does not apply the transformation matrix. + bool applyTransform = flags > D3DTTFF_COUNT1 && flags <= D3DTTFF_COUNT4; + + uint count = min(flags, 4u); + + // A projection component index of 4 means we won't do projection + uint projIndex = count != 0 ? count - 1 : 4; + + switch (inputFlags) { + default: + case (DXVK_TSS_TCI_PASSTHRU >> TCIOffset): + transformed = texCoords[inputIndex & 0xFF]; + + if (texcoordCount < 4) { + // Vulkan sets the w component to 1.0 if that's not provided by the vertex buffer, D3D9 expects 0 here + transformed.w = 0.0; + } + + if (applyTransform && !vertexHasPositionT()) { + /*This doesn't happen every time and I cannot figure out the difference between when it does and doesn't. + Keep it disabled for now, it's more likely that games rely on the zero texcoord than the weird 1 here. + if (texcoordCount <= 1) { + // y gets padded to 1 for some reason + transformed.y = 1.0; + }*/ + + if (texcoordCount >= 1 && texcoordCount < 4) { + // The first component after the last one thats backed by a vertex buffer gets padded to 1 for some reason. + uint idx = texcoordCount; + transformed[idx] = 1.0; + } + } else if (texcoordCount != 0 && !applyTransform) { + // COUNT0, COUNT1, COUNT > 4 => take count from vertex decl if that's not zero + count = texcoordCount; + } + + projIndex = count != 0 ? count - 1 : 4; + break; + + case (DXVK_TSS_TCI_CAMERASPACENORMAL >> TCIOffset): + transformed = outNrm; + if (!applyTransform) { + count = 3; + projIndex = 4; + } + break; + + case (DXVK_TSS_TCI_CAMERASPACEPOSITION >> TCIOffset): + transformed = vtx; + if (!applyTransform) { + count = 3; + projIndex = 4; + } + break; + + case (DXVK_TSS_TCI_CAMERASPACEREFLECTIONVECTOR >> TCIOffset): { + vec3 vtx3 = vtx.xyz; + vtx3 = normalize(vtx3); + + vec3 reflection = reflect(vtx3, normal); + transformed = vec4(reflection, 1.0); + if (!applyTransform) { + count = 3; + projIndex = 4; + } + break; + } + + case (DXVK_TSS_TCI_SPHEREMAP >> TCIOffset): { + vec3 vtx3 = vtx.xyz; + vtx3 = normalize(vtx3); + + vec3 reflection = reflect(vtx3, normal); + float m = length(reflection + vec3(0.0, 0.0, 1.0)) * 2.0; + + transformed = vec4( + reflection.x / m + 0.5, + reflection.y / m + 0.5, + 0.0, + 1.0 + ); + break; + } + } + + if (applyTransform && !vertexHasPositionT()) { + transformed = transformed * data.TexcoordMatrices[i]; + } + + // TODO: Shouldn't projected be checked per texture stage? + if (specUint(SpecSamplerProjected) != 0u && projIndex < 4) { + // The projection idx is always based on the flags, even when the input mode is not DXVK_TSS_TCI_PASSTHRU. + float projValue = transformed[projIndex]; + + // The w component is only used for projection or unused, so always insert the component that's supposed to be divided by there. + // The fragment shader will then decide whether to project or not. + transformed.w = projValue; + } + + // TODO: Shouldn't projected be checked per texture stage? + uint totalComponents = (specUint(SpecSamplerProjected) != 0u && projIndex < 4) ? 3 : 4; + for (uint j = count; j < totalComponents; j++) { + // Discard the components that exceed the specified D3DTTFF_COUNT + transformed[j] = 0.0; + } + + transformedTexCoords[i] = transformed; + } + + out_Texcoord0 = transformedTexCoords[0]; + out_Texcoord1 = transformedTexCoords[1]; + out_Texcoord2 = transformedTexCoords[2]; + out_Texcoord3 = transformedTexCoords[3]; + out_Texcoord4 = transformedTexCoords[4]; + out_Texcoord5 = transformedTexCoords[5]; + out_Texcoord6 = transformedTexCoords[6]; + out_Texcoord7 = transformedTexCoords[7]; + + if (useLighting()) { + vec4 diffuseValue = vec4(0.0); + vec4 specularValue = vec4(0.0); + vec4 ambientValue = vec4(0.0); + + for (uint i = 0; i < lightCount(); i++) { + D3D9Light light = data.Lights[i]; + + vec4 diffuse = light.Diffuse; + vec4 specular = light.Specular; + vec4 ambient = light.Ambient; + vec3 position = light.Position.xyz; + vec3 direction = light.Direction.xyz; + uint type = light.Type; + float range = light.Range; + float falloff = light.Falloff; + float atten0 = light.Attenuation0; + float atten1 = light.Attenuation1; + float atten2 = light.Attenuation2; + float theta = light.Theta; + float phi = light.Phi; + + bool isSpot = type == D3DLIGHT_SPOT; + bool isDirectional = type == D3DLIGHT_DIRECTIONAL; + + bvec3 isDirectional3 = bvec3(isDirectional); + + vec3 vtx3 = vtx.xyz; + + const bool useLegacyLights = specBool(SpecFFUseLegacyLights); + const bool isLegacyD3DLight2 = specBool(SpecFFIsLegacyD3DLight2); + + vec3 delta = position - vtx3; + float d = length(delta); + if (useLegacyLights && isLegacyD3DLight2) { + d = (range - d) / range; + } + vec3 hitDir = -direction; + hitDir = mix(delta, hitDir, isDirectional3); + hitDir = normalize(hitDir); + + float atten = fma(d, atten2, atten1); + atten = fma(d, atten, atten0); + if (!useLegacyLights) { + atten = 1.0 / atten; + } + atten = spvNMin(atten, FloatMaxValue); + + if (useLegacyLights && isLegacyD3DLight2) { + atten = d < 0.0 ? 0.0 : atten; // d > range + } else { + atten = d > range ? 0.0 : atten; + } + atten = isDirectional ? 1.0 : atten; + + // Spot Lighting + { + float rho = dot(-hitDir, direction); + float spotAtten = rho - phi; + spotAtten = spotAtten / (theta - phi); + spotAtten = pow(spotAtten, falloff); + + bool insideThetaAndPhi = rho <= theta; + bool insidePhi = rho > phi; + spotAtten = insidePhi ? spotAtten : 0.0; + spotAtten = insideThetaAndPhi ? spotAtten : 1.0; + spotAtten = clamp(spotAtten, 0.0, 1.0); + + spotAtten = atten * spotAtten; + atten = isSpot ? spotAtten : atten; + } + + float hitDot = dot(normal, hitDir); + hitDot = clamp(hitDot, 0.0, 1.0); + + float diffuseness = hitDot * atten; + + vec3 mid; + if (localViewer()) { + mid = normalize(vtx3); + mid = hitDir - mid; + } else { + mid = hitDir - vec3(0.0, 0.0, 1.0); + } + + mid = normalize(mid); + + float midDot = dot(normal, mid); + midDot = clamp(midDot, 0.0, 1.0); + bool doSpec = midDot > 0.0; + doSpec = doSpec && hitDot > 0.0; + if (useLegacyLights) { + doSpec = doSpec && data.Material.Power > 0.0; + } + + float specularness = pow(midDot, data.Material.Power); + specularness *= atten; + specularness = doSpec ? specularness : 0.0; + + vec4 lightAmbient = ambient * atten; + vec4 lightDiffuse = diffuse * diffuseness; + vec4 lightSpecular = specular * specularness; + + ambientValue += lightAmbient; + diffuseValue += lightDiffuse; + specularValue += lightSpecular; + } + + vec4 matDiffuse = pickMaterialSource(diffuseSource(), data.Material.Diffuse); + vec4 matAmbient = pickMaterialSource(ambientSource(), data.Material.Ambient); + vec4 matEmissive = pickMaterialSource(emissiveSource(), data.Material.Emissive); + vec4 matSpecular = pickMaterialSource(specularSource(), data.Material.Specular); + + vec4 finalColor0 = fma(matAmbient, data.GlobalAmbient, matEmissive); + finalColor0 = fma(matAmbient, ambientValue, finalColor0); + finalColor0 = fma(matDiffuse, diffuseValue, finalColor0); + finalColor0.a = matDiffuse.a; + + vec4 finalColor1 = matSpecular * specularValue; + + // Saturate + finalColor0 = clamp(finalColor0, vec4(0.0), vec4(1.0)); + + finalColor1 = clamp(finalColor1, vec4(0.0), vec4(1.0)); + + out_Color0 = finalColor0; + if (specularEnabled()) { + out_Color1 = finalColor1; + } else { + out_Color1 = vertexHasColor1() ? in_Color1 : vec4(0.0, 0.0, 0.0, 1.0); + // TODO: SM3 behavior, see below. + } + } else { + out_Color0 = vertexHasColor0() ? in_Color0 : vec4(1.0, 1.0, 1.0, 1.0); + out_Color1 = vertexHasColor1() ? in_Color1 : vec4(0.0, 0.0, 0.0, 1.0); + // TODO: If it's used with a SM3 PS, we need to export 0,0,0,0 as the default for color1. + // Implement that using a spec constant. + } + + out_Fog = calculateFog(vtx, vec4(0.0)); + + gl_PointSize = calculatePointSize(vtx); + + // We statically declare 6 clip planes, so we always need to write values. + emitVsClipping(vtx); +} diff --git a/src/ddraw/d3d3/d3d3_device.cpp b/src/ddraw/d3d3/d3d3_device.cpp new file mode 100644 index 00000000000..15cee3b96da --- /dev/null +++ b/src/ddraw/d3d3/d3d3_device.cpp @@ -0,0 +1,1418 @@ +#include "d3d3_device.h" + +#include "../d3d_common_texture.h" + +#include "d3d3_execute_buffer.h" + +#include "../ddraw/ddraw_surface.h" + +#include "../d3d5/d3d5_device.h" + +#include +#include + +namespace dxvk { + + uint32_t D3D3Device::s_deviceCount = 0; + + D3D3Device::D3D3Device( + Com&& d3d3DeviceProxy, + DDrawSurface* pParent, + D3DDEVICEDESC3 Desc, + GUID deviceGUID, + d3d9::D3DPRESENT_PARAMETERS Params9, + Com&& pDevice9, + DWORD CreationFlags9) + : DDrawWrappedObject(pParent, std::move(d3d3DeviceProxy), std::move(pDevice9)) + , m_commonIntf ( pParent->GetCommonInterface() ) + , m_multithread ( CreationFlags9 & D3DCREATE_MULTITHREADED ) + , m_params9 ( Params9 ) + , m_desc ( Desc ) + , m_deviceGUID ( deviceGUID ) + , m_rt ( pParent ) { + // Get the bridge interface to D3D9 + if (unlikely(FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8Bridge), reinterpret_cast(&m_bridge))))) { + throw DxvkError("D3D3Device: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + m_totalMemory = m_bridge->DetermineInitialTextureMemory(); + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->emulateFSAA == FSAAEmulation::Forced)) { + Logger::warn("D3D3Device: Force enabling AA"); + m_d3d9->SetRenderState(d3d9::D3DRS_MULTISAMPLEANTIALIAS, TRUE); + } + + if (m_commonIntf->GetD3D5Device() == nullptr) { + // The default value of D3DRENDERSTATE_TEXTUREMAPBLEND in D3D3 is D3DTBLEND_MODULATE + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + } + + m_deviceCount = ++s_deviceCount; + + Logger::debug(str::format("D3D3Device: Created a new device nr. ((1-", m_deviceCount, "))")); + } + + D3D3Device::~D3D3Device() { + // Dissasociate every bound viewport from this device + for (auto viewport : m_viewports) { + viewport->GetCommonViewport()->SetD3D3Device(nullptr); + } + + // Clear the common interface device pointer if it points to this device + if (m_commonIntf->GetD3D3Device() == this) + m_commonIntf->SetD3D3Device(nullptr); + + Logger::debug(str::format("D3D3Device: Device nr. ((1-", m_deviceCount, ")) bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D3Device::QueryInterface(REFIID riid, void** ppvObject) { + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(riid == __uuidof(IDirect3DDevice2))) { + if (likely(m_commonIntf->GetD3D5Device() != nullptr)) { + Logger::debug("D3D3Device::QueryInterface: Query for existing IDirect3DDevice2"); + return m_commonIntf->GetD3D5Device()->QueryInterface(riid, ppvObject); + } + + // A D3D3 device shouldn't be able to create a D3D5 device + // if it doesn't previously exist as a parent/origin device + Logger::warn("D3D3Device::QueryInterface: Query for IDirect3DDevice2"); + return E_NOINTERFACE; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE D3D3Device::GetCaps(D3DDEVICEDESC *hal_desc, D3DDEVICEDESC *hel_desc) { + Logger::debug(">>> D3D3Device::GetCaps"); + + if (unlikely(hal_desc == nullptr || hel_desc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!IsValidD3DDeviceDescSize(hal_desc->dwSize) + || !IsValidD3DDeviceDescSize(hel_desc->dwSize))) + return DDERR_INVALIDPARAMS; + + D3DDEVICEDESC3 desc_HAL = m_desc; + D3DDEVICEDESC3 desc_HEL = m_desc; + + if (m_deviceGUID == IID_IDirect3DRGBDevice) { + desc_HAL.dwFlags = 0; + desc_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + desc_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL; + desc_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL; + desc_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + desc_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + } else if (m_deviceGUID == IID_IDirect3DHALDevice) { + desc_HEL.dcmColorModel = 0; + } else { + Logger::warn("D3D3Device::GetCaps: Unhandled device type"); + } + + memcpy(hal_desc, &desc_HAL, hal_desc->dwSize); + memcpy(hel_desc, &desc_HEL, hel_desc->dwSize); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::SwapTextureHandles(IDirect3DTexture *tex1, IDirect3DTexture *tex2) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::SwapTextureHandles"); + + D3D3Texture* texture1 = static_cast(tex1); + D3D3Texture* texture2 = static_cast(tex2); + + D3DCommonTexture* commonTex1 = texture1->GetCommonTexture(); + D3DCommonTexture* commonTex2 = texture2->GetCommonTexture(); + + const D3DTEXTUREHANDLE handle1 = commonTex1->GetTextureHandle(); + const D3DTEXTUREHANDLE handle2 = commonTex2->GetTextureHandle(); + + m_parent->GetCommonInterface()->ReleaseTextureHandle(handle1); + m_parent->GetCommonInterface()->ReleaseTextureHandle(handle2); + + commonTex1->SetTextureHandle(handle2); + commonTex2->SetTextureHandle(handle1); + + m_commonIntf->EmplaceTexture(commonTex1, handle2); + m_commonIntf->EmplaceTexture(commonTex2, handle1); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::GetStats(D3DSTATS *stats) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::GetStats"); + + if (unlikely(stats == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(stats->dwSize != sizeof(D3DSTATS))) + return DDERR_INVALIDPARAMS; + + const DWORD dwSize = stats->dwSize; + + *stats = m_stats; + stats->dwSize = dwSize; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::AddViewport(IDirect3DViewport *viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::AddViewport"); + + if (unlikely(viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D3Viewport* d3d3Viewport = static_cast(viewport); + HRESULT hr = m_proxy->AddViewport(d3d3Viewport->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + AddViewportInternal(viewport); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::DeleteViewport(IDirect3DViewport *viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::DeleteViewport"); + + if (unlikely(viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D3Viewport* d3d3Viewport = static_cast(viewport); + HRESULT hr = m_proxy->DeleteViewport(d3d3Viewport->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + DeleteViewportInternal(viewport); + + // Clear the current viewport if it is deleted from the device + if (m_currentViewport.ptr() == d3d3Viewport) + m_currentViewport = nullptr; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::NextViewport(IDirect3DViewport *lpDirect3DViewport, IDirect3DViewport **lplpAnotherViewport, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::NextViewport"); + + if (unlikely(lplpAnotherViewport == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpAnotherViewport); + + if (flags & D3DNEXT_HEAD) { + if (likely(m_viewports.size() > 0)) + *lplpAnotherViewport = m_viewports.front().ref(); + } else if (flags & D3DNEXT_NEXT) { + if (unlikely(lpDirect3DViewport == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(m_viewports.size() > 0)) + Logger::warn("D3D3Device::NextViewport: Unimplemented D3DNEXT_NEXT flag"); + } else if (flags & D3DNEXT_TAIL) { + if (likely(m_viewports.size() > 0)) + *lplpAnotherViewport = m_viewports.back().ref(); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::EnumTextureFormats(LPD3DENUMTEXTUREFORMATSCALLBACK cb, void *ctx) { + Logger::debug(">>> D3D3Device::EnumTextureFormats"); + + if (unlikely(cb == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + DDSURFACEDESC textureFormat = { }; + textureFormat.dwSize = sizeof(DDSURFACEDESC); + textureFormat.dwFlags = DDSD_CAPS | DDSD_PIXELFORMAT; + textureFormat.ddsCaps.dwCaps = DDSCAPS_TEXTURE; + + // Note: The list of formats exposed in D3D3 is restricted to the below + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_X1R5G5B5); + HRESULT hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_A1R5G5B5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + // D3DFMT_X4R4G4B4 is not supported by D3D3 + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_A4R4G4B4); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_R5G6B5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_X8R8G8B8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_A8R8G8B8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + // Not supported in D3D9, but some games need + // it to be advertised (for offscreen plain surfaces?) + if (unlikely(d3dOptions->supportR3G3B2)) { + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_R3G3B2); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + } + + // Not supported in D3D9, but some games may use it + /*textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_P8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK;*/ + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::BeginScene() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::BeginScene"); + + RefreshLastUsedDevice(); + + if (unlikely(m_inScene)) + return D3DERR_SCENE_IN_SCENE; + + HRESULT hr = m_d3d9->BeginScene(); + + if (likely(SUCCEEDED(hr))) + m_inScene = true; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::EndScene() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::EndScene"); + + RefreshLastUsedDevice(); + + if (unlikely(!m_inScene)) + return D3DERR_SCENE_NOT_IN_SCENE; + + HRESULT hr = m_d3d9->EndScene(); + + if (likely(SUCCEEDED(hr))) { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (d3dOptions->forceProxiedPresent) { + // If we have drawn anything, we need to make sure we blit back + // the results onto the D3D3 render target before we flip it + if (m_commonIntf->HasDrawn()) + BlitToDDrawSurface(m_rt->GetProxied(), m_rt->GetD3D9()); + + m_rt->GetProxied()->Flip(static_cast(m_commonIntf->GetFlipRTSurface()), + m_commonIntf->GetFlipRTFlags()); + + if (likely(d3dOptions->backBufferGuard != D3DBackBufferGuard::Strict)) + m_commonIntf->ResetDrawTracking(); + } + + m_inScene = false; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::GetDirect3D(IDirect3D **d3d) { + Logger::debug(">>> D3D3Device::GetDirect3D"); + + if (unlikely(d3d == nullptr)) + return DDERR_INVALIDPARAMS; + + *d3d = ref(m_commonIntf->GetD3D3Interface()); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::Initialize(IDirect3D *d3d, GUID *lpGUID, D3DDEVICEDESC *desc) { + Logger::debug("<<< D3D3Device::Initialize: Proxy"); + + if (unlikely(d3d == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D3Interface* d3d3Intf = static_cast(d3d); + return m_proxy->Initialize(d3d3Intf->GetProxied(), lpGUID, desc); + } + + HRESULT STDMETHODCALLTYPE D3D3Device::CreateExecuteBuffer(D3DEXECUTEBUFFERDESC *desc, IDirect3DExecuteBuffer **buffer, IUnknown *pkOuter) { + Logger::debug(">>> D3D3Device::CreateExecuteBuffer"); + + if (unlikely(desc == nullptr || buffer == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(buffer); + + Com bufferProxy; + *buffer = ref(new D3D3ExecuteBuffer(std::move(bufferProxy), *desc, this)); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::Execute(IDirect3DExecuteBuffer *buffer, IDirect3DViewport *viewport, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::Execute"); + + if (unlikely(buffer == nullptr || viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D3ExecuteBuffer* d3d3ExecuteBuffer = static_cast(buffer); + D3D3Viewport* d3d3Viewport = static_cast(viewport); + + if (m_currentViewport != d3d3Viewport) + m_currentViewport = d3d3Viewport; + + D3DEXECUTEDATA data = d3d3ExecuteBuffer->GetExecuteData(); + std::vector executeBuffer = d3d3ExecuteBuffer->GetBuffer(); + if (unlikely(executeBuffer.size() == 0)) + return DDERR_INVALIDPARAMS; + + uint8_t* buf = executeBuffer.data(); + const D3DTLVERTEX* vertexBuffer = reinterpret_cast(&buf[data.dwVertexOffset]); + uint8_t* ptr = buf + data.dwInstructionOffset; + uint8_t* end = ptr + data.dwInstructionLength; + + while (ptr < end - sizeof(D3DINSTRUCTION)) { + D3DINSTRUCTION* instruction = reinterpret_cast(ptr); + const uint8_t size = instruction->bSize; + const uint16_t count = instruction->wCount; + const uint32_t instructionSize = sizeof(D3DINSTRUCTION) + (count * size); + DWORD opcode = instruction->bOpcode; + uint8_t* operation = ptr + sizeof(D3DINSTRUCTION); + + if (opcode == D3DOP_EXIT) + break; + + if (unlikely(ptr + instructionSize > end)) { + Logger::warn("D3D3Device::Execute: Reached the end but found no D3DOP_EXIT!"); + break; + } + + switch (opcode) { + case D3DOP_BRANCHFORWARD: { + Logger::debug("D3D3Device::Execute: D3DOP_BRANCHFORWARD"); + + D3DBRANCH* branch = reinterpret_cast(operation); + for (uint16_t i = 0; i < count; i++) { + const D3DBRANCH& b = branch[i]; + + bool masked = (data.dsStatus.dwStatus & b.dwMask) == b.dwValue; + if (b.bNegate) { + masked = !masked; + } + + if (masked && b.dwOffset) { + ptr+= branch->dwOffset - instructionSize; + } + } + + break; + } + case D3DOP_LINE: { + Logger::debug("D3D3Device::Execute: D3DOP_LINE"); + + D3DLINE* line = reinterpret_cast(operation); + DrawLineInternal(line, count, data.dwVertexCount, vertexBuffer); + + break; + } + case D3DOP_POINT: { + Logger::debug("D3D3Device::Execute: D3DOP_POINT"); + + D3DPOINT* point = reinterpret_cast(operation); + DrawPointInternal(point, count, data.dwVertexCount, vertexBuffer); + + break; + } + case D3DOP_TRIANGLE: { + Logger::debug("D3D3Device::Execute: D3DOP_TRIANGLE"); + + D3DTRIANGLE* triangle = reinterpret_cast(operation); + DrawTriangleInternal(triangle, count, data.dwVertexCount, vertexBuffer); + + break; + } + case D3DOP_MATRIXLOAD: { + static bool s_matrixLoadErrorShown; + if (!std::exchange(s_matrixLoadErrorShown, true)) + Logger::warn("D3D3Device::Execute: D3DOP_MATRIXLOAD is not implemented"); + break; + } + case D3DOP_MATRIXMULTIPLY: { + static bool s_matrixMultiplyErrorShown; + if (!std::exchange(s_matrixMultiplyErrorShown, true)) + Logger::warn("D3D3Device::Execute: D3DOP_MATRIXMULTIPLY is not implemented"); + break; + } + case D3DOP_PROCESSVERTICES: { + D3DPROCESSVERTICES* processVertices = reinterpret_cast(operation); + + for (uint16_t i = 0; i < count; i++) { + D3DPROCESSVERTICES& pv = processVertices[i]; + if (pv.dwFlags & D3DPROCESSVERTICES_COPY) { + static bool s_pvCopyErrorShown; + // Appears to be mostly harmless + if (!std::exchange(s_pvCopyErrorShown, true)) + Logger::debug("D3D3Device::Execute: D3DOP_PROCESSVERTICES COPY is not implemented"); + } + // D3DPROCESSVERTICES_NOCOLOR and D3DPROCESSVERTICES_UPDATEEXTENTS are additional flags for transforms + if (pv.dwFlags & D3DPROCESSVERTICES_TRANSFORM) { + static bool s_pvTransformErrorShown; + if (!std::exchange(s_pvTransformErrorShown, true)) + Logger::warn("D3D3Device::Execute: D3DOP_PROCESSVERTICES TRANSFORM is not implemented"); + } + if (pv.dwFlags & D3DPROCESSVERTICES_TRANSFORMLIGHT) { + static bool s_pvTransformLightErrorShown; + if (!std::exchange(s_pvTransformLightErrorShown, true)) + Logger::warn("D3D3Device::Execute: D3DOP_PROCESSVERTICES TRANSORMLIGHT is not implemented"); + } + } + + break; + } + case D3DOP_SPAN: { + Logger::warn("D3D3Device::Execute: D3DOP_SPAN"); + + D3DSPAN* span = reinterpret_cast(operation); + DrawSpanInternal(span, count, data.dwVertexCount, vertexBuffer); + + break; + } + case D3DOP_STATELIGHT: { + Logger::debug("D3D3Device::Execute: D3DOP_STATELIGHT"); + + D3DSTATE* state = reinterpret_cast(operation); + for (uint16_t i = 0; i < count; i++) { + const D3DSTATE& s = state[i]; + SetLightStateInternal(s.dlstLightStateType, s.dwArg[0]); + } + break; + } + case D3DOP_STATERENDER: { + Logger::debug("D3D3Device::Execute: D3DOP_STATERENDER"); + + D3DSTATE* state = reinterpret_cast(operation); + for (uint16_t i = 0; i < count; i++) { + const D3DSTATE& s = state[i]; + SetRenderStateInternal(s.drstRenderStateType, s.dwArg[0]); + } + + break; + } + case D3DOP_STATETRANSFORM: { + Logger::debug("D3D3Device::Execute: D3DOP_STATETRANSFORM"); + + D3DSTATE* state = reinterpret_cast(operation); + D3DMATRIX matrix; + for (uint16_t i = 0; i < count; i++) { + const D3DSTATE& s = state[i]; + + if (unlikely(s.dwArg[0] == 0)) + continue; + + HRESULT hr = GetMatrix(s.dwArg[0], &matrix); + if (unlikely(FAILED(hr))) + continue; + + hr = m_d3d9->SetTransform(ConvertTransformState(s.dtstTransformStateType), &matrix); + if (likely(SUCCEEDED(hr))) { + if (s.dtstTransformStateType == D3DTRANSFORMSTATE_WORLD) { + m_worldHandle = s.dwArg[0]; + } else if (s.dtstTransformStateType == D3DTRANSFORMSTATE_VIEW) { + m_viewHandle = s.dwArg[0]; + } else if (s.dtstTransformStateType == D3DTRANSFORMSTATE_PROJECTION) { + m_projectionHandle = s.dwArg[0]; + } + } else { + Logger::warn("D3D3Device::Execute: Failed to set D3D9 transform"); + } + } + + break; + } + case D3DOP_SETSTATUS: { + Logger::debug("D3D3Device::Execute: D3DOP_SETSTATUS"); + + D3DSTATUS* status = reinterpret_cast(operation); + for (uint16_t i = 0; i < count; i++) { + data.dsStatus = status[i]; + } + + break; + } + case D3DOP_TEXTURELOAD: { + Logger::debug("D3D3Device::Execute: D3DOP_TEXTURELOAD"); + + D3DTEXTURELOAD* textureLoad = reinterpret_cast(operation); + TextureLoadInternal(textureLoad, count); + + break; + } + default: + Logger::err(str::format("D3D3Device::Execute: Unknown opcode encountered: ", opcode)); + break; + } + + ptr += instructionSize; + } + + m_commonIntf->UpdateDrawTracking(); + + return D3D_OK; + } + + // Equivalent of the later ZVISIBLE render state + HRESULT STDMETHODCALLTYPE D3D3Device::Pick(IDirect3DExecuteBuffer *buffer, IDirect3DViewport *viewport, DWORD flags, D3DRECT *rect) { + D3DDeviceLock lock = LockDevice(); + + Logger::warn("!!! D3D3Device::Pick: Stub"); + + if (unlikely(buffer == nullptr || viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::GetPickRecords(DWORD *count, D3DPICKRECORD *records) { + D3DDeviceLock lock = LockDevice(); + + Logger::warn("!!! D3D3Device::GetPickRecords: Stub"); + + if (unlikely(!count)) + return D3D_OK; + + if (unlikely(records == nullptr)) + return DDERR_INVALIDPARAMS; + + D3DPICKRECORD newRecords = { }; + + *records = newRecords; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::CreateMatrix(D3DMATRIXHANDLE *matrix) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::CreateMatrix"); + + m_matrixHandle++; + m_matrices.emplace(std::piecewise_construct, + std::forward_as_tuple(m_matrixHandle), + std::forward_as_tuple(D3DMATRIX())); + + *matrix = m_matrixHandle; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::SetMatrix(D3DMATRIXHANDLE handle, D3DMATRIX *matrix) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::SetMatrix"); + + if (unlikely(matrix == nullptr)) + return DDERR_INVALIDPARAMS; + + auto matrixIter = m_matrices.find(handle); + + if (likely(matrixIter != m_matrices.end())) { + matrixIter->second = *matrix; + } else { + Logger::warn("D3D3Device::SetMatrix: Matrix not found"); + return DDERR_INVALIDPARAMS; + } + + // Update D3D9 transforms if the updated matrix is in use + D3DTRANSFORMSTATETYPE transformType = D3DTRANSFORMSTATETYPE(0); + + if (m_worldHandle == handle) { + transformType = D3DTRANSFORMSTATE_WORLD; + } else if (m_viewHandle == handle) { + transformType = D3DTRANSFORMSTATE_VIEW; + } else if (m_projectionHandle == handle) { + transformType = D3DTRANSFORMSTATE_PROJECTION; + } + + if (transformType) { + HRESULT hr = m_d3d9->SetTransform(ConvertTransformState(transformType), matrix); + if (unlikely(FAILED(hr))) + Logger::warn("D3D3Device::SetMatrix: Failed to update D3D9 transform"); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::GetMatrix(D3DMATRIXHANDLE handle, D3DMATRIX *matrix) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::GetMatrix"); + + if (unlikely(matrix == nullptr)) + return DDERR_INVALIDPARAMS; + + auto matrixIter = m_matrices.find(handle); + + if (likely(matrixIter != m_matrices.end())) { + *matrix = matrixIter->second; + } else { + Logger::warn(str::format("D3D3Device::GetMatrix: Matrix not found: ", handle)); + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Device::DeleteMatrix(D3DMATRIXHANDLE D3DMatHandle) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D3Device::DeleteMatrix"); + + auto matrixIter = m_matrices.find(D3DMatHandle); + + if (likely(matrixIter != m_matrices.end())) { + m_matrices.erase(matrixIter); + } else { + Logger::warn("D3D3Device::DeleteMatrix: Matrix not found"); + return DDERR_INVALIDPARAMS; + } + + if (m_worldHandle == D3DMatHandle) { + m_worldHandle = 0; + } else if (m_viewHandle == D3DMatHandle) { + m_viewHandle = 0; + } else if (m_projectionHandle == D3DMatHandle) { + m_projectionHandle = 0; + } + + return D3D_OK; + } + + void D3D3Device::InitializeDS() { + if (!m_rt->IsInitialized()) + m_rt->InitializeD3D9RenderTarget(); + + m_ds = m_rt->GetAttachedDepthStencil(); + + if (m_ds != nullptr) { + Logger::debug("D3D3Device::InitializeDS: Found an attached DS"); + + HRESULT hrDS = m_ds->InitializeD3D9DepthStencil(); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D3Device::InitializeDS: Failed to initialize D3D9 DS"); + } else if (m_commonIntf->GetD3D5Device() == nullptr) { + Logger::info("D3D3Device::InitializeDS: Got depth stencil from RT"); + + DDSURFACEDESC descDS; + descDS.dwSize = sizeof(DDSURFACEDESC); + m_ds->GetProxied()->GetSurfaceDesc(&descDS); + Logger::debug(str::format("D3D3Device::InitializeDS: DepthStencil: ", descDS.dwWidth, "x", descDS.dwHeight)); + + HRESULT hrDS9 = m_d3d9->SetDepthStencilSurface(m_ds->GetD3D9()); + if(unlikely(FAILED(hrDS9))) { + Logger::err("D3D3Device::InitializeDS: Failed to set D3D9 depth stencil"); + } else { + // This needs to act like an auto depth stencil of sorts, so manually enable z-buffering + m_d3d9->SetRenderState(d3d9::D3DRS_ZENABLE, d3d9::D3DZB_TRUE); + } + } + } else if (m_commonIntf->GetD3D5Device() == nullptr) { + Logger::info("D3D3Device::InitializeDS: RT has no depth stencil attached"); + m_d3d9->SetDepthStencilSurface(nullptr); + // Should be superfluous, but play it safe + m_d3d9->SetRenderState(d3d9::D3DRS_ZENABLE, d3d9::D3DZB_FALSE); + } + } + + inline void D3D3Device::AddViewportInternal(IDirect3DViewport* viewport) { + D3D3Viewport* d3d3Viewport = static_cast(viewport); + + auto it = std::find(m_viewports.begin(), m_viewports.end(), d3d3Viewport); + if (unlikely(it != m_viewports.end())) { + Logger::warn("D3D3Device::AddViewportInternal: Pre-existing viewport found"); + } else { + m_viewports.push_back(d3d3Viewport); + d3d3Viewport->GetCommonViewport()->SetD3D3Device(this); + } + } + + inline void D3D3Device::DeleteViewportInternal(IDirect3DViewport* viewport) { + D3D3Viewport* d3d3Viewport = static_cast(viewport); + + auto it = std::find(m_viewports.begin(), m_viewports.end(), d3d3Viewport); + if (likely(it != m_viewports.end())) { + m_viewports.erase(it); + d3d3Viewport->GetCommonViewport()->SetD3D3Device(nullptr); + } else { + Logger::warn("D3D3Device::DeleteViewportInternal: Viewport not found"); + } + } + + inline HRESULT STDMETHODCALLTYPE D3D3Device::SetLightStateInternal(D3DLIGHTSTATETYPE dwLightStateType, DWORD dwLightState) { + Logger::debug(">>> D3D3Device::SetLightStateInternal"); + + switch (dwLightStateType) { + case D3DLIGHTSTATE_MATERIAL: { + D3D5Device* device5 = m_commonIntf->GetD3D5Device(); + + if (unlikely(!dwLightState)) { + m_materialHandle = dwLightState; + + if (device5 != nullptr) + device5->SetCurrentMaterialHandle(dwLightState); + + return D3D_OK; + } + + Logger::debug(str::format("D3D3Device::SetLightStateInternal: Applying material nr. ", dwLightState, " to D3D9")); + + D3D3Interface* d3d3Intf = m_commonIntf->GetD3D3Interface(); + // consider pure D3D3 device use by default + if (likely(d3d3Intf != nullptr)) { + d3d9::D3DMATERIAL9* material9 = d3d3Intf->GetCommonD3DInterface()->GetD3D9MaterialFromHandle(dwLightState); + if (unlikely(material9 == nullptr)) + return DDERR_INVALIDPARAMS; + + m_materialHandle = dwLightState; + m_d3d9->SetMaterial(material9); + // fall back to using a D3D5 device otherwise + } else if (likely(device5 != nullptr)) { + d3d9::D3DMATERIAL9* material9 = device5->GetParent()->GetCommonD3DInterface()->GetD3D9MaterialFromHandle(dwLightState); + + device5->SetCurrentMaterialHandle(dwLightState); + device5->GetD3D9()->SetMaterial(material9); + } else { + Logger::warn("D3D3Device::SetLightStateInternal: Unable to set D3D9 material"); + } + + break; + } + case D3DLIGHTSTATE_AMBIENT: + m_d3d9->SetRenderState(d3d9::D3DRS_AMBIENT, dwLightState); + break; + case D3DLIGHTSTATE_COLORMODEL: + if (unlikely(dwLightState != D3DCOLOR_RGB)) + Logger::warn("D3D3Device::SetLightStateInternal: Unsupported D3DLIGHTSTATE_COLORMODEL"); + break; + case D3DLIGHTSTATE_FOGMODE: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGVERTEXMODE, dwLightState); + break; + case D3DLIGHTSTATE_FOGSTART: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGSTART, dwLightState); + break; + case D3DLIGHTSTATE_FOGEND: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGEND, dwLightState); + break; + case D3DLIGHTSTATE_FOGDENSITY: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGDENSITY, dwLightState); + break; + default: + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + inline HRESULT STDMETHODCALLTYPE D3D3Device::SetRenderStateInternal(D3DRENDERSTATETYPE dwRenderStateType, DWORD dwRenderState) { + Logger::debug(str::format(">>> D3D3Device::SetRenderStateInternal: ", dwRenderStateType)); + + // As opposed to D3D7, D3D3 does not error out on + // unknown or invalid render states. + if (unlikely(!IsValidD3D3RenderStateType(dwRenderStateType))) { + Logger::debug(str::format("D3D3Device::SetRenderStateInternal: Invalid render state ", dwRenderStateType)); + return D3D_OK; + } + + d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(dwRenderStateType); + + switch (dwRenderStateType) { + // Most render states translate 1:1 to D3D9 + default: + break; + + // Replacement for later implemented SetTexture calls + case D3DRENDERSTATE_TEXTUREHANDLE: { + DDrawSurface* surface = nullptr; + + if (likely(dwRenderState != 0)) { + surface = m_commonIntf->GetSurfaceFromTextureHandle(dwRenderState); + if (unlikely(surface == nullptr)) + return DDERR_INVALIDPARAMS; + } + + HRESULT hr = SetTextureInternal(surface, dwRenderState); + if (unlikely(FAILED(hr))) + return hr; + + break; + } + + case D3DRENDERSTATE_ANTIALIAS: { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (likely(d3dOptions->emulateFSAA == FSAAEmulation::Disabled)) { + if (unlikely(dwRenderState)) + Logger::warn("D3D3Device::SetRenderStateInternal: Device does not expose FSAA emulation"); + return D3D_OK; + } + + State9 = d3d9::D3DRS_MULTISAMPLEANTIALIAS; + break; + } + + case D3DRENDERSTATE_TEXTUREADDRESS: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, dwRenderState); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSV, dwRenderState); + return D3D_OK; + + // Always enabled on later APIs, though default FALSE in D3D3 + case D3DRENDERSTATE_TEXTUREPERSPECTIVE: + return D3D_OK; + + // Not implemented in DXVK, but forward it anyway + case D3DRENDERSTATE_WRAPU: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + if (dwRenderState == TRUE) { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & D3DWRAP_U); + } else { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & ~D3DWRAP_U); + } + return D3D_OK; + } + + // Not implemented in DXVK, but forward it anyway + case D3DRENDERSTATE_WRAPV: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + if (dwRenderState == TRUE) { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & D3DWRAP_V); + } else { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & ~D3DWRAP_V); + } + return D3D_OK; + } + + // TODO: Implement D3DRS_LINEPATTERN - vkCmdSetLineRasterizationModeEXT + // and advertise support with D3DPRASTERCAPS_PAT once that is done + case D3DRENDERSTATE_LINEPATTERN: + static bool s_linePatternErrorShown; + + if (!std::exchange(s_linePatternErrorShown, true)) + Logger::warn("D3D3Device::SetRenderStateInternal: Unimplemented render state D3DRS_LINEPATTERN"); + + return D3D_OK; + + case D3DRENDERSTATE_MONOENABLE: + static bool s_monoEnableErrorShown; + + if (dwRenderState && !std::exchange(s_monoEnableErrorShown, true)) + Logger::warn("D3D3Device::SetRenderStateInternal: Unimplemented render state D3DRENDERSTATE_MONOENABLE"); + + return D3D_OK; + + case D3DRENDERSTATE_ROP2: + static bool s_ROP2ErrorShown; + + if (!std::exchange(s_ROP2ErrorShown, true)) + Logger::warn("D3D3Device::SetRenderStateInternal: Unimplemented render state D3DRENDERSTATE_ROP2"); + + return D3D_OK; + + // "This render state is not supported by the software rasterizers, and is often ignored by hardware drivers." + case D3DRENDERSTATE_PLANEMASK: + return D3D_OK; + + // Docs: "[...] only the first two (D3DFILTER_NEAREST and + // D3DFILTER_LINEAR) are valid with D3DRENDERSTATE_TEXTUREMAG." + case D3DRENDERSTATE_TEXTUREMAG: { + switch (dwRenderState) { + case D3DFILTER_NEAREST: + case D3DFILTER_LINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MAGFILTER, dwRenderState); + break; + default: + break; + } + return D3D_OK; + } + + case D3DRENDERSTATE_TEXTUREMIN: { + switch (dwRenderState) { + case D3DFILTER_NEAREST: + case D3DFILTER_LINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, dwRenderState); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_NONE); + break; + // "The closest mipmap level is chosen and a point filter is applied." + case D3DFILTER_MIPNEAREST: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_POINT); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_POINT); + break; + // "The closest mipmap level is chosen and a bilinear filter is applied within it." + case D3DFILTER_MIPLINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_LINEAR); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_POINT); + break; + // "The two closest mipmap levels are chosen and then a linear + // blend is used between point filtered samples of each level." + case D3DFILTER_LINEARMIPNEAREST: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_POINT); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_LINEAR); + break; + // "The two closest mipmap levels are chosen and then combined using a bilinear filter." + case D3DFILTER_LINEARMIPLINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_LINEAR); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_LINEAR); + break; + default: + break; + } + return D3D_OK; + } + + case D3DRENDERSTATE_TEXTUREMAPBLEND: + m_textureMapBlend = dwRenderState; + + switch (dwRenderState) { + // "In this mode, the RGB and alpha values of the texture replace + // the colors that would have been used with no texturing." + case D3DTBLEND_DECAL: + case D3DTBLEND_COPY: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_CURRENT); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_CURRENT); + break; + // "In this mode, the RGB values of the texture are multiplied with the RGB values + // that would have been used with no texturing. Any alpha values in the texture + // replace the alpha values in the colors that would have been used with no texturing; + // if the texture does not contain an alpha component, alpha values at the vertices + // in the source are interpolated between vertices." + case D3DTBLEND_MODULATE: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "In this mode, the RGB and alpha values of the texture are blended with the colors + // that would have been used with no texturing, according to the following formulas [...]" + case D3DTBLEND_DECALALPHA: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_BLENDTEXTUREALPHA); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "In this mode, the RGB values of the texture are multiplied with the RGB values that + // would have been used with no texturing, and the alpha values of the texture + // are multiplied with the alpha values that would have been used with no texturing." + case D3DTBLEND_MODULATEALPHA: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "Add the Gouraud interpolants to the texture lookup with saturation semantics + // (that is, if the color value overflows it is set to the maximum possible value)." + case D3DTBLEND_ADD: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_ADD); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // Unsupported + default: + case D3DTBLEND_DECALMASK: + case D3DTBLEND_MODULATEMASK: + break; + } + + return D3D_OK; + + // Replaced by D3DRENDERSTATE_ALPHABLENDENABLE + case D3DRENDERSTATE_BLENDENABLE: + State9 = d3d9::D3DRS_ALPHABLENDENABLE; + break; + + // Safe to ignore. Docs state: "Direct3D's retained mode uses this operation as a + // quick-reject test: it does the z-visible test on the bounding box of a set of + // primitives and only renders them if it returns TRUE." + case D3DRENDERSTATE_ZVISIBLE: + return D3D_OK; + + // Docs state: "Most hardware either doesn't support it (always off) or + // always supports it (always on).", and "All hardware should be subpixel correct. + // Some software rasterizers are not subpixel correct because of the performance loss." + case D3DRENDERSTATE_SUBPIXEL: + case D3DRENDERSTATE_SUBPIXELX: + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEDALPHA: + static bool s_stippledAlphaErrorShown; + + if (dwRenderState && !std::exchange(s_stippledAlphaErrorShown, true)) + Logger::warn("D3D3Device::SetRenderStateInternal: Unimplemented render state D3DRENDERSTATE_STIPPLEDALPHA"); + + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEENABLE: + static bool s_stippleEnableErrorShown; + + if (dwRenderState && !std::exchange(s_stippleEnableErrorShown, true)) + Logger::warn("D3D3Device::SetRenderStateInternal: Unimplemented render state D3DRENDERSTATE_STIPPLEENABLE"); + + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEPATTERN00: + case D3DRENDERSTATE_STIPPLEPATTERN01: + case D3DRENDERSTATE_STIPPLEPATTERN02: + case D3DRENDERSTATE_STIPPLEPATTERN03: + case D3DRENDERSTATE_STIPPLEPATTERN04: + case D3DRENDERSTATE_STIPPLEPATTERN05: + case D3DRENDERSTATE_STIPPLEPATTERN06: + case D3DRENDERSTATE_STIPPLEPATTERN07: + case D3DRENDERSTATE_STIPPLEPATTERN08: + case D3DRENDERSTATE_STIPPLEPATTERN09: + case D3DRENDERSTATE_STIPPLEPATTERN10: + case D3DRENDERSTATE_STIPPLEPATTERN11: + case D3DRENDERSTATE_STIPPLEPATTERN12: + case D3DRENDERSTATE_STIPPLEPATTERN13: + case D3DRENDERSTATE_STIPPLEPATTERN14: + case D3DRENDERSTATE_STIPPLEPATTERN15: + case D3DRENDERSTATE_STIPPLEPATTERN16: + case D3DRENDERSTATE_STIPPLEPATTERN17: + case D3DRENDERSTATE_STIPPLEPATTERN18: + case D3DRENDERSTATE_STIPPLEPATTERN19: + case D3DRENDERSTATE_STIPPLEPATTERN20: + case D3DRENDERSTATE_STIPPLEPATTERN21: + case D3DRENDERSTATE_STIPPLEPATTERN22: + case D3DRENDERSTATE_STIPPLEPATTERN23: + case D3DRENDERSTATE_STIPPLEPATTERN24: + case D3DRENDERSTATE_STIPPLEPATTERN25: + case D3DRENDERSTATE_STIPPLEPATTERN26: + case D3DRENDERSTATE_STIPPLEPATTERN27: + case D3DRENDERSTATE_STIPPLEPATTERN28: + case D3DRENDERSTATE_STIPPLEPATTERN29: + case D3DRENDERSTATE_STIPPLEPATTERN30: + case D3DRENDERSTATE_STIPPLEPATTERN31: + static bool s_stipplePatternErrorShown; + + if (!std::exchange(s_stipplePatternErrorShown, true)) + Logger::warn("D3D3Device::SetRenderStateInternal: Unimplemented render state D3DRENDERSTATE_STIPPLEPATTERN"); + + return D3D_OK; + } + + // This call will never fail + return m_d3d9->SetRenderState(State9, dwRenderState); + } + + inline void D3D3Device::DrawTriangleInternal(D3DTRIANGLE* triangle, DWORD count, DWORD vertexCount, const D3DTLVERTEX* vertexBuffer) { + std::vector vertices; + + for (DWORD i = 0; i < count; i++) { + const D3DTRIANGLE& t = triangle[i]; + + if (t.v1 >= vertexCount || t.v2 >= vertexCount || t.v3 >= vertexCount) + continue; + + // TODO: Ignoring t.wFlags for now as they are relevant only for wireframe mode? + // (D3DTRIFLAG_START, D3DTRIFLAG_STARTFLAT(1-29), D3DTRIFLAG_ODD(strip), + // D3DTRIFLAG_EVEN(fan) and D3DTRIFLAG_EDGEENABLE). + + vertices.push_back(vertexBuffer[t.v1]); + vertices.push_back(vertexBuffer[t.v2]); + vertices.push_back(vertexBuffer[t.v3]); + } + + if (!vertices.empty() && m_d3d9 != nullptr) { + HandlePreDrawLegacyProjection(); + + m_d3d9->SetFVF(D3DFVF_TLVERTEX); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPT_TRIANGLELIST, + GetPrimitiveCount(D3DPT_TRIANGLELIST, vertices.size()), + vertices.data(), + GetFVFSize(D3DFVF_TLVERTEX)); + + HandlePostDrawLegacyProjection(); + + if (SUCCEEDED(hr)) { + Logger::debug(str::format("D3D3Device::Execute: D3DOP_TRIANGLE drawn vertices: ", vertices.size())); + m_stats.dwTrianglesDrawn += std::max(vertices.size() / 3, 0u); + } else { + Logger::err(str::format("D3D3Device::Execute: D3DOP_TRIANGLE failed to draw vertices: ", vertices.size())); + } + + vertices.clear(); + } + } + + inline void D3D3Device::DrawLineInternal(D3DLINE* line, DWORD count, DWORD vertexCount, const D3DTLVERTEX* vertexBuffer) { + std::vector vertices; + + for (DWORD i = 0; i < count; i++) { + const D3DLINE& l = line[i]; + + if (l.v1 >= vertexCount || l.v2 >= vertexCount) + continue; + + vertices.push_back(vertexBuffer[l.v1]); + vertices.push_back(vertexBuffer[l.v2]); + } + + if (!vertices.empty() && m_d3d9 != nullptr) { + HandlePreDrawLegacyProjection(); + + m_d3d9->SetFVF(D3DFVF_TLVERTEX); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPT_LINELIST, + GetPrimitiveCount(D3DPT_LINELIST, vertices.size()), + vertices.data(), + GetFVFSize(D3DFVF_TLVERTEX)); + + HandlePostDrawLegacyProjection(); + + if (SUCCEEDED(hr)) { + Logger::debug(str::format("D3D3Device::Execute: D3DOP_LINE drawn vertices: ", vertices.size())); + m_stats.dwLinesDrawn += std::max(vertices.size() / 2, 0u); + } else { + Logger::err(str::format("D3D3Device::Execute: D3DOP_LINE failed to draw vertices: ", vertices.size())); + } + + vertices.clear(); + } + } + + inline void D3D3Device::DrawPointInternal(D3DPOINT* point, DWORD count, DWORD vertexCount, const D3DTLVERTEX* vertexBuffer) { + std::vector vertices; + + for (DWORD i = 0; i < count; i++) { + const D3DPOINT& p = point[i]; + + if (p.wFirst >= vertexCount) + continue; + + for (DWORD x = 0; x < std::min(static_cast(p.wCount), vertexCount - p.wFirst); x++) { + vertices.push_back(vertexBuffer[p.wFirst + x]); + } + } + + if (!vertices.empty() && m_d3d9 != nullptr) { + HandlePreDrawLegacyProjection(); + + m_d3d9->SetFVF(D3DFVF_TLVERTEX); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPT_POINTLIST, + GetPrimitiveCount(D3DPT_POINTLIST, vertices.size()), + vertices.data(), + GetFVFSize(D3DFVF_TLVERTEX)); + + HandlePostDrawLegacyProjection(); + + if (SUCCEEDED(hr)) { + Logger::debug(str::format("D3D3Device::Execute: D3DOP_POINT drawn vertices: ", vertices.size())); + m_stats.dwPointsDrawn += static_cast(vertices.size()); + } else { + Logger::err(str::format("D3D3Device::Execute: D3DOP_POINT failed to draw vertices: ", vertices.size())); + } + vertices.clear(); + } + } + + inline void D3D3Device::DrawSpanInternal(D3DSPAN* span, DWORD count, DWORD vertexCount, const D3DTLVERTEX* vertexBuffer) { + std::vector vertices; + + for (DWORD i = 0; i < count; i++) { + const D3DSPAN& s = span[i]; + + if (s.wFirst >= vertexCount) + continue; + + for (DWORD x = 0; x < std::min(static_cast(s.wCount), vertexCount - s.wFirst); x++) { + vertices.push_back(vertexBuffer[s.wFirst + x]); + } + } + + if (!vertices.empty() && m_d3d9 != nullptr) { + HandlePreDrawLegacyProjection(); + + m_d3d9->SetFVF(D3DFVF_TLVERTEX); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPT_LINESTRIP, + GetPrimitiveCount(D3DPT_LINESTRIP, vertices.size()), + vertices.data(), + GetFVFSize(D3DFVF_TLVERTEX)); + + HandlePostDrawLegacyProjection(); + + if (SUCCEEDED(hr)) { + Logger::debug(str::format("D3D3Device::Execute: D3DOP_SPAN drawn vertices: ", vertices.size())); + m_stats.dwSpansDrawn += std::max(vertices.size() - 1, 0u); + } else { + Logger::err(str::format("D3D3Device::Execute: D3DOP_SPAN failed to draw vertices: ", vertices.size())); + } + + vertices.clear(); + } + } + + inline void D3D3Device::TextureLoadInternal(D3DTEXTURELOAD* textureLoad, DWORD count) { + for (DWORD i = 0; i < count; i++) { + const D3DTEXTURELOAD& tl = textureLoad[i]; + + DDrawSurface* destSurf = m_commonIntf->GetSurfaceFromTextureHandle(tl.hDestTexture); + DDrawSurface* srcSurf = m_commonIntf->GetSurfaceFromTextureHandle(tl.hSrcTexture); + if (destSurf != nullptr && srcSurf != nullptr) { + destSurf->GetD3D3Texture()->Load(srcSurf->GetD3D3Texture()); + } else { + Logger::warn("D3D3Device::Execute: D3DOP_TEXTURELOAD source or/and destination texture is null"); + } + } + } + + inline HRESULT D3D3Device::SetTextureInternal(DDrawSurface* surface, DWORD textureHandle) { + Logger::debug(">>> D3D3Device::SetTextureInternal"); + + HRESULT hr; + + // Unbinding texture stages + if (surface == nullptr) { + Logger::debug("D3D3Device::SetTextureInternal: Unbiding D3D9 texture"); + + hr = m_d3d9->SetTexture(0, nullptr); + + if (likely(SUCCEEDED(hr))) { + if (m_textureHandle != 0) { + Logger::debug("D3D3Device::SetTextureInternal: Unbinding local texture"); + m_textureHandle = 0; + } + } else { + Logger::err("D3D3Device::SetTextureInternal: Failed to unbind D3D9 texture"); + } + + return hr; + } + + Logger::debug("D3D3Device::SetTextureInternal: Binding D3D9 texture"); + + // Only upload textures if any sort of blit/lock operation + // has been performed on them since the last SetTexture call, + // or textures which have been used on a different device, and + // need their D3D9 object to be reinitialized at this point + if (surface->GetCommonSurface()->HasDirtyMipMaps() || + unlikely(surface->GetD3D9Device() != m_d3d9.ptr())) { + hr = surface->InitializeOrUploadD3D9(); + if (unlikely(FAILED(hr))) { + Logger::err("D3D3Device::SetTextureInternal: Failed to initialize/upload D3D9 texture"); + return hr; + } + + surface->GetCommonSurface()->UnDirtyMipMaps(); + } else { + Logger::debug("D3D3Device::SetTextureInternal: Skipping upload of texture and mip maps"); + } + + // Only fast skip on D3D9 side, since we want to ensure + // color keying is applied properly even in the case + // of the same texture being set again (color key may change) + //if (unlikely(m_textureHandle == textureHandle)) + //return D3D_OK; + + d3d9::IDirect3DTexture9* tex9 = surface->GetD3D9Texture(); + + if (likely(tex9 != nullptr)) { + hr = m_d3d9->SetTexture(0, tex9); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D3Device::SetTextureInternal: Failed to bind D3D9 texture"); + return hr; + } + + // "Any alpha values in the texture replace the alpha values in the colors that would + // have been used with no texturing; if the texture does not contain an alpha component, + // alpha values at the vertices in the source are interpolated between vertices." + if (m_textureMapBlend == D3DTBLEND_MODULATE) { + const DWORD textureOp = surface->GetCommonSurface()->IsAlphaFormat() ? D3DTOP_SELECTARG1 : D3DTOP_MODULATE; + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, textureOp); + } + + // D3D3 enables color key transparency globally + const bool validColorKey = surface->GetCommonSurface()->HasValidColorKey(); + m_bridge->SetColorKeyState(validColorKey); + if (validColorKey) { + Logger::debug("D3D3Device::SetTextureInternal: Enabling color key transparency"); + DDCOLORKEY normalizedColorKey = surface->GetCommonSurface()->GetColorKeyNormalized(); + m_bridge->SetColorKey(normalizedColorKey.dwColorSpaceLowValue, + normalizedColorKey.dwColorSpaceHighValue); + } + } + + m_textureHandle = textureHandle; + + return D3D_OK; + } + +} diff --git a/src/ddraw/d3d3/d3d3_device.h b/src/ddraw/d3d3/d3d3_device.h new file mode 100644 index 00000000000..f0cba7e33ee --- /dev/null +++ b/src/ddraw/d3d3/d3d3_device.h @@ -0,0 +1,211 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_options.h" + +#include "../d3d_multithread.h" +#include "../ddraw_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +#include "d3d3_interface.h" +#include "d3d3_viewport.h" + +#include +#include + +namespace dxvk { + + class DDrawSurface; + + /** + * \brief D3D3 device implementation + */ + class D3D3Device final : public DDrawWrappedObject { + + public: + D3D3Device( + Com&& d3d3DeviceProxy, + DDrawSurface* pParent, + D3DDEVICEDESC3 Desc, + GUID deviceGUID, + d3d9::D3DPRESENT_PARAMETERS Params9, + Com&& pDevice9, + DWORD CreationFlags9); + + ~D3D3Device(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE GetCaps(D3DDEVICEDESC *hal_desc, D3DDEVICEDESC *hel_desc); + + HRESULT STDMETHODCALLTYPE SwapTextureHandles(IDirect3DTexture *tex1, IDirect3DTexture *tex2); + + HRESULT STDMETHODCALLTYPE GetStats(D3DSTATS *stats); + + HRESULT STDMETHODCALLTYPE AddViewport(IDirect3DViewport *viewport); + + HRESULT STDMETHODCALLTYPE DeleteViewport(IDirect3DViewport *viewport); + + HRESULT STDMETHODCALLTYPE NextViewport(IDirect3DViewport *lpDirect3DViewport, IDirect3DViewport **lplpAnotherViewport, DWORD flags); + + HRESULT STDMETHODCALLTYPE EnumTextureFormats(LPD3DENUMTEXTUREFORMATSCALLBACK cb, void *ctx); + + HRESULT STDMETHODCALLTYPE BeginScene(); + + HRESULT STDMETHODCALLTYPE EndScene(); + + HRESULT STDMETHODCALLTYPE GetDirect3D(IDirect3D **d3d); + + HRESULT STDMETHODCALLTYPE Initialize(IDirect3D *d3d, GUID *lpGUID, D3DDEVICEDESC *desc); + + HRESULT STDMETHODCALLTYPE CreateExecuteBuffer(D3DEXECUTEBUFFERDESC *desc, IDirect3DExecuteBuffer **buffer, IUnknown *pkOuter); + + HRESULT STDMETHODCALLTYPE Execute(IDirect3DExecuteBuffer *buffer, IDirect3DViewport *viewport, DWORD flags); + + HRESULT STDMETHODCALLTYPE Pick(IDirect3DExecuteBuffer *buffer, IDirect3DViewport *viewport, DWORD flags, D3DRECT *rect); + + HRESULT STDMETHODCALLTYPE GetPickRecords(DWORD *count, D3DPICKRECORD *records); + + HRESULT STDMETHODCALLTYPE CreateMatrix(D3DMATRIXHANDLE *matrix); + + HRESULT STDMETHODCALLTYPE SetMatrix(D3DMATRIXHANDLE handle, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE GetMatrix(D3DMATRIXHANDLE handle, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE DeleteMatrix(D3DMATRIXHANDLE D3DMatHandle); + + void InitializeDS(); + + D3DDeviceLock LockDevice() { + return m_multithread.AcquireLock(); + } + + void EnableLegacyLights(bool isD3DLight2) { + m_bridge->SetLegacyLightsState(true, isD3DLight2); + } + + uint32_t GetTotalTextureMemory() const { + return m_totalMemory; + } + + D3DSTATS GetStatsInternal() const { + return m_stats; + } + + d3d9::D3DPRESENT_PARAMETERS GetPresentParameters() const { + return m_params9; + } + + d3d9::D3DMULTISAMPLE_TYPE GetMultiSampleType() const { + return m_params9.MultiSampleType; + } + + DDrawSurface* GetRenderTarget() const { + return m_rt.ptr(); + } + + DDrawSurface* GetDepthStencil() const { + return m_ds.ptr(); + } + + D3D3Viewport* GetCurrentViewportInternal() const { + return m_currentViewport.ptr(); + } + + D3DMATERIALHANDLE GetCurrentMaterialHandle() const { + return m_materialHandle; + } + + private: + + inline void RefreshLastUsedDevice() { + if (unlikely(m_commonIntf->GetD3D3Device() != this)) + m_commonIntf->SetD3D3Device(this); + } + + inline void AddViewportInternal(IDirect3DViewport* viewport); + + inline void DeleteViewportInternal(IDirect3DViewport* viewport); + + inline HRESULT SetTextureInternal(DDrawSurface* surface, DWORD textureHandle); + + inline HRESULT STDMETHODCALLTYPE SetRenderStateInternal(D3DRENDERSTATETYPE dwRenderStateType, DWORD dwRenderState); + + inline HRESULT STDMETHODCALLTYPE SetLightStateInternal(D3DLIGHTSTATETYPE dwLightStateType, DWORD dwLightState); + + inline void DrawTriangleInternal(D3DTRIANGLE* triangle, DWORD count, DWORD vertexCount, const D3DTLVERTEX* vertexBuffer); + + inline void DrawLineInternal(D3DLINE* line, DWORD count, DWORD vertexCount, const D3DTLVERTEX* vertexBuffer); + + inline void DrawPointInternal(D3DPOINT* point, DWORD count, DWORD vertexCount, const D3DTLVERTEX* vertexBuffer); + + inline void DrawSpanInternal(D3DSPAN* span, DWORD count, DWORD vertexCount, const D3DTLVERTEX* vertexBuffer); + + inline void TextureLoadInternal(D3DTEXTURELOAD* textureLoad, DWORD count); + + inline void HandlePreDrawLegacyProjection() { + if (likely(m_currentViewport != nullptr)) { + m_legacyProjection = m_currentViewport->GetCommonViewport()->GetLegacyProjectionMatrix(0); + + if (m_legacyProjection != nullptr) { + //Logger::debug("D3D3Device: Applying legacy projection"); + m_d3d9->GetTransform(d3d9::D3DTS_PROJECTION, &m_projectionMatrix); + m_d3d9->MultiplyTransform(d3d9::D3DTS_PROJECTION, m_legacyProjection); + } + } + } + + inline void HandlePostDrawLegacyProjection() { + if (m_legacyProjection != nullptr) { + //Logger::debug("D3D3Device: Reverting legacy projection"); + m_d3d9->SetTransform(d3d9::D3DTS_PROJECTION, &m_projectionMatrix); + } + } + + bool m_inScene = false; + + static uint32_t s_deviceCount; + uint32_t m_deviceCount = 0; + + uint32_t m_totalMemory = 0; + + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_bridge; + + D3DMultithread m_multithread; + + d3d9::D3DPRESENT_PARAMETERS m_params9; + + D3DMATERIALHANDLE m_materialHandle = 0; + D3DTEXTUREHANDLE m_textureHandle = 0; + + D3DDEVICEDESC3 m_desc; + GUID m_deviceGUID; + + Com m_rt; + Com m_ds; + + Com m_currentViewport; + std::vector> m_viewports; + + // Value of D3DRENDERSTATE_TEXTUREMAPBLEND + DWORD m_textureMapBlend = D3DTBLEND_MODULATE; + + D3DMATRIX m_projectionMatrix = { }; + const D3DMATRIX* m_legacyProjection = nullptr; + + D3DSTATS m_stats = { }; + + D3DMATRIXHANDLE m_worldHandle = 0; + D3DMATRIXHANDLE m_viewHandle = 0; + D3DMATRIXHANDLE m_projectionHandle = 0; + + std::atomic m_matrixHandle = 0; + std::unordered_map m_matrices; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d3/d3d3_execute_buffer.cpp b/src/ddraw/d3d3/d3d3_execute_buffer.cpp new file mode 100644 index 00000000000..fa64217a9ec --- /dev/null +++ b/src/ddraw/d3d3/d3d3_execute_buffer.cpp @@ -0,0 +1,105 @@ +#include "d3d3_execute_buffer.h" + +namespace dxvk { + + uint32_t D3D3ExecuteBuffer::s_buffCount = 0; + + D3D3ExecuteBuffer::D3D3ExecuteBuffer( + Com&& buffProxy, + D3DEXECUTEBUFFERDESC desc, + D3D3Device* pParent) + : DDrawWrappedObject(pParent, std::move(buffProxy), nullptr) + , m_desc (desc) { + if (likely(m_buffer.size() == 0 && (m_desc.dwFlags & D3DDEB_BUFSIZE))) { + m_buffer.resize(m_desc.dwBufferSize); + Logger::debug(str::format("D3D3ExecuteBuffer: Buffer is initialized with size ", m_desc.dwBufferSize)); + } + + m_buffCount = ++s_buffCount; + + Logger::debug(str::format("D3D3ExecuteBuffer: Created a new execute buffer nr. {{1-", m_buffCount, "}}:")); + } + + D3D3ExecuteBuffer::~D3D3ExecuteBuffer() { + Logger::debug(str::format("D3D3ExecuteBuffer: Execute buffer nr. {{1-", m_buffCount, "}} bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D3ExecuteBuffer::GetExecuteData(LPD3DEXECUTEDATA lpData) { + Logger::debug(">>> D3D3ExecuteBuffer::GetExecuteData"); + + if (unlikely(lpData == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(m_buffer.size() == 0)) + return DDERR_INVALIDPARAMS; + + *lpData = m_data; + + return D3D_OK; + } + + // Docs state: "Returns DDERR_ALREADYINITIALIZED because the + // Direct3DExecuteBuffer object is initialized when it is created." + HRESULT STDMETHODCALLTYPE D3D3ExecuteBuffer::Initialize(LPDIRECT3DDEVICE lpDirect3DDevice, LPD3DEXECUTEBUFFERDESC lpDesc) { + Logger::debug(">>> D3D3ExecuteBuffer::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE D3D3ExecuteBuffer::Lock(LPD3DEXECUTEBUFFERDESC lpDesc) { + Logger::debug(">>> D3D3ExecuteBuffer::Lock"); + + if (unlikely(lpDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(m_locked)) + return D3DERR_EXECUTE_LOCKED; + + m_locked = true; + lpDesc->dwFlags = D3DDEB_BUFSIZE|D3DDEB_LPDATA; + lpDesc->dwBufferSize = m_buffer.size(); + lpDesc->lpData = m_buffer.data(); + + return D3D_OK; + } + + // Docs state: "Not currently implemented." + HRESULT STDMETHODCALLTYPE D3D3ExecuteBuffer::Optimize(DWORD dwUnknown) { + Logger::debug(">>> D3D3ExecuteBuffer::Optimize"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE D3D3ExecuteBuffer::SetExecuteData(LPD3DEXECUTEDATA lpData) { + Logger::debug(">>> D3D3ExecuteBuffer::SetExecuteData"); + + if (unlikely(lpData == nullptr || m_buffer.size() == 0)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpData->dwInstructionOffset + lpData->dwInstructionLength > m_buffer.size())) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpData->dwVertexOffset + lpData->dwVertexCount > m_buffer.size())) + return DDERR_INVALIDPARAMS; + + m_data = *lpData; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3ExecuteBuffer::Unlock() { + Logger::debug(">>> D3D3ExecuteBuffer::Unlock"); + + if (unlikely(!m_locked)) + return D3DERR_EXECUTE_NOT_LOCKED; + + m_locked = false; + + return D3D_OK; + } + + // Docs state: "Not currently implemented." + HRESULT STDMETHODCALLTYPE D3D3ExecuteBuffer::Validate(LPDWORD lpdwOffset, LPD3DVALIDATECALLBACK lpFunc, LPVOID lpUserArg, DWORD dwReserved) { + Logger::debug(">>> D3D3ExecuteBuffer::Validate"); + return DDERR_UNSUPPORTED; + } + +} diff --git a/src/ddraw/d3d3/d3d3_execute_buffer.h b/src/ddraw/d3d3/d3d3_execute_buffer.h new file mode 100644 index 00000000000..2b1c1dc41bb --- /dev/null +++ b/src/ddraw/d3d3/d3d3_execute_buffer.h @@ -0,0 +1,59 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "d3d3_device.h" + +#include + +namespace dxvk { + + class D3D3ExecuteBuffer final : public DDrawWrappedObject { + + public: + + D3D3ExecuteBuffer( + Com&& buffProxy, + D3DEXECUTEBUFFERDESC desc, + D3D3Device* pParent); + + ~D3D3ExecuteBuffer(); + + HRESULT STDMETHODCALLTYPE GetExecuteData(LPD3DEXECUTEDATA lpData); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECT3DDEVICE lpDirect3DDevice, LPD3DEXECUTEBUFFERDESC lpDesc); + + HRESULT STDMETHODCALLTYPE Lock(LPD3DEXECUTEBUFFERDESC lpDesc); + + HRESULT STDMETHODCALLTYPE Optimize(DWORD dwUnknown); + + HRESULT STDMETHODCALLTYPE SetExecuteData(LPD3DEXECUTEDATA lpData); + + HRESULT STDMETHODCALLTYPE Unlock(); + + HRESULT STDMETHODCALLTYPE Validate(LPDWORD lpdwOffset, LPD3DVALIDATECALLBACK lpFunc, LPVOID lpUserArg, DWORD dwReserved); + + std::vector GetBuffer() const { + return m_buffer; + } + + D3DEXECUTEDATA GetExecuteData() const { + return m_data; + } + + private: + + bool m_locked = false; + + static uint32_t s_buffCount; + uint32_t m_buffCount = 0; + + D3DEXECUTEBUFFERDESC m_desc; + D3DEXECUTEDATA m_data; + D3D3Device* m_D3D3Device = nullptr; + + std::vector m_buffer; + }; + +} diff --git a/src/ddraw/d3d3/d3d3_interface.cpp b/src/ddraw/d3d3/d3d3_interface.cpp new file mode 100644 index 00000000000..0376f554c84 --- /dev/null +++ b/src/ddraw/d3d3/d3d3_interface.cpp @@ -0,0 +1,337 @@ +#include "d3d3_interface.h" + +#include "d3d3_material.h" +#include "d3d3_viewport.h" + +#include "../d3d_light.h" + +#include "../d3d5/d3d5_interface.h" + +#include "../ddraw/ddraw_interface.h" + +namespace dxvk { + + uint32_t D3D3Interface::s_intfCount = 0; + + D3D3Interface::D3D3Interface( + DDrawCommonInterface* commonIntf, + D3DCommonInterface* commonD3DIntf, + Com&& d3d3IntfProxy, + IUnknown* pParent) + : DDrawWrappedObject(pParent, std::move(d3d3IntfProxy), std::move(d3d9::Direct3DCreate9(D3D_SDK_VERSION))) + , m_commonIntf ( commonIntf ) + , m_commonD3DIntf ( commonD3DIntf ) { + // Get the bridge interface to D3D9. + if (unlikely(FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8InterfaceBridge), reinterpret_cast(&m_bridge))))) { + throw DxvkError("D3D3Interface: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + if (m_commonD3DIntf == nullptr) + m_commonD3DIntf = new D3DCommonInterface(); + + m_commonD3DIntf->SetD3D3Interface(this); + + m_bridge->EnableD3D3CompatibilityMode(); + + m_intfCount = ++s_intfCount; + + Logger::debug(str::format("D3D3Interface: Created a new interface nr. ((1-", m_intfCount, "))")); + } + + D3D3Interface::~D3D3Interface() { + if (m_commonD3DIntf->GetD3D3Interface() == this) + m_commonD3DIntf->SetD3D3Interface(nullptr); + + // Needed for D3D3 device creation from an IDirectDrawSurface object + if (m_commonIntf->GetD3D3Interface() == this) + m_commonIntf->SetD3D3Interface(nullptr); + + Logger::debug(str::format("D3D3Interface: Interface nr. ((1-", m_intfCount, ")) bites the dust")); + } + + // Interlocked refcount with the parent IDirectDraw + ULONG STDMETHODCALLTYPE D3D3Interface::AddRef() { + if (likely(m_parent != nullptr)) { + IUnknown* origin = m_commonIntf->GetOrigin(); + if (likely(origin != nullptr)) + return origin->AddRef(); + else + return m_parent->AddRef(); + } else { + return ComObjectClamp::AddRef(); + } + } + + // Interlocked refcount with the parent IDirectDraw + ULONG STDMETHODCALLTYPE D3D3Interface::Release() { + if (likely(m_parent != nullptr)) { + IUnknown* origin = m_commonIntf->GetOrigin(); + if (likely(origin != nullptr)) + return origin->Release(); + else + return m_parent->Release(); + } else { + return ComObjectClamp::Release(); + } + } + + HRESULT STDMETHODCALLTYPE D3D3Interface::QueryInterface(REFIID riid, void** ppvObject) { + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (riid == __uuidof(IDirectDraw)) { + Logger::debug("D3D3Interface::QueryInterface: Query for IDirectDraw"); + return m_parent->QueryInterface(riid, ppvObject); + } + // Deathtrap Dungeon queries for IDirect3D2... not sure if this ever worked + if (unlikely(riid == __uuidof(IDirect3D2))) { + if (likely(m_commonD3DIntf->GetD3D5Interface() != nullptr)) { + Logger::debug("D3D3Interface::QueryInterface: Query for existing IDirect3D2"); + return m_commonD3DIntf->GetD3D5Interface()->QueryInterface(riid, ppvObject); + } + + Logger::warn("D3D3Interface::QueryInterface: Query for IDirect3D2"); + return m_parent->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // Docs state: "This method is provided for compliance with the COM protocol. + // Returns DDERR_ALREADYINITIALIZED because the Direct3D object is initialized when it is created." + HRESULT STDMETHODCALLTYPE D3D3Interface::Initialize(REFIID riid) { + Logger::debug(">>> D3D3Interface::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE D3D3Interface::EnumDevices(LPD3DENUMDEVICESCALLBACK lpEnumDevicesCallback, LPVOID lpUserArg) { + Logger::debug(">>> D3D3Interface::EnumDevices"); + + if (unlikely(lpEnumDevicesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // D3D3 reports both HAL and HEL caps for any type of device, + // with minor differences between the two. Note that the + // device listing order matters, so list RAMP first, RGB second, + // and HAL last. A RAMP device also needs to be advertised in D3D3, + // since some games like Resident Evil expect it to be present. + + // RAMP device (monochrome), this is expected to be exposed + GUID guidRAMP = IID_IDirect3DRampDevice; + D3DDEVICEDESC3 desc3RAMP_HAL = GetD3D3Caps(d3dOptions); + D3DDEVICEDESC3 desc3RAMP_HEL = desc3RAMP_HAL; + D3DDEVICEDESC descRAMP_HAL = { }; + D3DDEVICEDESC descRAMP_HEL = { }; + desc3RAMP_HAL.dwFlags = 0; + desc3RAMP_HAL.dcmColorModel = 0; + // RAMP devices use a monochrome color model + desc3RAMP_HEL.dcmColorModel = D3DCOLOR_MONO; + // Some applications apparently care about RGB texture caps + desc3RAMP_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc3RAMP_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc3RAMP_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + desc3RAMP_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + memcpy(&descRAMP_HAL, &desc3RAMP_HAL, sizeof(D3DDEVICEDESC3)); + memcpy(&descRAMP_HEL, &desc3RAMP_HEL, sizeof(D3DDEVICEDESC3)); + static char deviceDescRAMP[100] = "D3VK RAMP"; + static char deviceNameRAMP[100] = "D3VK RAMP"; + + HRESULT hr = lpEnumDevicesCallback(&guidRAMP, &deviceDescRAMP[0], &deviceNameRAMP[0], + &descRAMP_HAL, &descRAMP_HEL, lpUserArg); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + // Software emulation, this is expected to be exposed + GUID guidRGB = IID_IDirect3DRGBDevice; + D3DDEVICEDESC3 desc3RGB_HAL = GetD3D3Caps(d3dOptions); + D3DDEVICEDESC3 desc3RGB_HEL = desc3RGB_HAL; + D3DDEVICEDESC descRGB_HAL = { }; + D3DDEVICEDESC descRGB_HEL = { }; + desc3RGB_HAL.dwFlags = 0; + desc3RGB_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + desc3RGB_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc3RGB_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc3RGB_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + desc3RGB_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + memcpy(&descRGB_HAL, &desc3RGB_HAL, sizeof(D3DDEVICEDESC3)); + memcpy(&descRGB_HEL, &desc3RGB_HEL, sizeof(D3DDEVICEDESC3)); + static char deviceDescRGB[100] = "D3VK RGB"; + static char deviceNameRGB[100] = "D3VK RGB"; + + hr = lpEnumDevicesCallback(&guidRGB, &deviceDescRGB[0], &deviceNameRGB[0], + &descRGB_HAL, &descRGB_HEL, lpUserArg); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + // Hardware acceleration + GUID guidHAL = IID_IDirect3DHALDevice; + D3DDEVICEDESC3 desc3HAL_HAL = GetD3D3Caps(d3dOptions); + D3DDEVICEDESC3 desc3HAL_HEL = desc3HAL_HAL; + D3DDEVICEDESC descHAL_HAL = { }; + D3DDEVICEDESC descHAL_HEL = { }; + desc3HAL_HEL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + desc3HAL_HEL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc3HAL_HEL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + memcpy(&descHAL_HAL, &desc3HAL_HAL, sizeof(D3DDEVICEDESC3)); + memcpy(&descHAL_HEL, &desc3HAL_HEL, sizeof(D3DDEVICEDESC3)); + static char deviceDescHAL[100] = "D3VK HAL"; + static char deviceNameHAL[100] = "D3VK HAL"; + + hr = lpEnumDevicesCallback(&guidHAL, &deviceDescHAL[0], &deviceNameHAL[0], + &descHAL_HAL, &descHAL_HEL, lpUserArg); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Interface::CreateLight(LPDIRECT3DLIGHT *lplpDirect3DLight, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D3Interface::CreateLight"); + + if (unlikely(lplpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DLight); + + *lplpDirect3DLight = ref(new D3DLight(nullptr, this)); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Interface::CreateMaterial(LPDIRECT3DMATERIAL *lplpDirect3DMaterial, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D3Interface::CreateMaterial"); + + if (unlikely(lplpDirect3DMaterial == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DMaterial); + + D3DMATERIALHANDLE handle = m_commonD3DIntf->GetNextMaterialHandle(); + Com d3d3Material = new D3D3Material(nullptr, this, handle); + m_commonD3DIntf->EmplaceMaterial(d3d3Material->GetCommonMaterial(), handle); + + *lplpDirect3DMaterial = d3d3Material.ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Interface::CreateViewport(LPDIRECT3DVIEWPORT *lplpD3DViewport, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D3Interface::CreateViewport"); + + Com lplpD3DViewportProxy; + HRESULT hr = m_proxy->CreateViewport(&lplpD3DViewportProxy, pUnkOuter); + if (unlikely(FAILED(hr))) + return hr; + + InitReturnPtr(lplpD3DViewport); + + *lplpD3DViewport = ref(new D3D3Viewport(nullptr, std::move(lplpD3DViewportProxy), this)); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Interface::FindDevice(D3DFINDDEVICESEARCH *lpD3DFDS, D3DFINDDEVICERESULT *lpD3DFDR) { + Logger::debug(">>> D3D3Interface::FindDevice"); + + if (unlikely(lpD3DFDS == nullptr || lpD3DFDR == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpD3DFDS->dwSize != sizeof(D3DFINDDEVICESEARCH))) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // Software emulation, this is expected to be exposed + D3DDEVICEDESC3 descRGB_HAL = GetD3D3Caps(d3dOptions); + D3DDEVICEDESC3 descRGB_HEL = descRGB_HAL; + descRGB_HAL.dwFlags = 0; + descRGB_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + descRGB_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + descRGB_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + descRGB_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + descRGB_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + + // Hardware acceleration + D3DDEVICEDESC3 descHAL_HAL = GetD3D3Caps(d3dOptions); + D3DDEVICEDESC3 descHAL_HEL = descHAL_HAL; + descHAL_HEL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + descHAL_HEL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + descHAL_HEL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + descHAL_HEL.dwDevCaps &= ~D3DDEVCAPS_HWTRANSFORMANDLIGHT + & ~D3DDEVCAPS_DRAWPRIMITIVES2 + & ~D3DDEVCAPS_DRAWPRIMITIVES2EX; + + D3DFINDDEVICERESULT3 lpD3DFRD3 = { }; + lpD3DFRD3.dwSize = sizeof(D3DFINDDEVICERESULT3); + + if (lpD3DFDS->dwFlags & D3DFDS_GUID) { + Logger::debug("D3D3Interface::FindDevice: Matching by device GUID"); + + if (lpD3DFDS->guid == IID_IDirect3DRGBDevice || + lpD3DFDS->guid == IID_IDirect3DMMXDevice || + lpD3DFDS->guid == IID_IDirect3DRampDevice) { + Logger::debug("D3D3Interface::FindDevice: Matched IID_IDirect3DRGBDevice"); + lpD3DFRD3.guid = IID_IDirect3DRGBDevice; + lpD3DFRD3.ddHwDesc = descRGB_HAL; + lpD3DFRD3.ddSwDesc = descRGB_HEL; + } else if (lpD3DFDS->guid == IID_IDirect3DHALDevice) { + Logger::debug("D3D3Interface::FindDevice: Matched IID_IDirect3DHALDevice"); + lpD3DFRD3.guid = IID_IDirect3DHALDevice; + lpD3DFRD3.ddHwDesc = descHAL_HAL; + lpD3DFRD3.ddSwDesc = descHAL_HEL; + } else { + Logger::err(str::format("D3D3Interface::FindDevice: Unknown device type: ", lpD3DFDS->guid)); + return DDERR_NOTFOUND; + } + + memcpy(lpD3DFDR, &lpD3DFRD3, sizeof(D3DFINDDEVICERESULT3)); + } else if (lpD3DFDS->dwFlags & D3DFDS_HARDWARE) { + Logger::debug("D3D3Interface::FindDevice: Matching by hardware flag"); + + if (likely(lpD3DFDS->bHardware == TRUE)) { + Logger::debug("D3D3Interface::FindDevice: Matched IID_IDirect3DHALDevice"); + lpD3DFRD3.guid = IID_IDirect3DHALDevice; + lpD3DFRD3.ddHwDesc = descHAL_HAL; + lpD3DFRD3.ddSwDesc = descHAL_HEL; + } else { + Logger::debug("D3D3Interface::FindDevice: Matched IID_IDirect3DRGBDevice"); + lpD3DFRD3.guid = IID_IDirect3DRGBDevice; + lpD3DFRD3.ddHwDesc = descRGB_HAL; + lpD3DFRD3.ddSwDesc = descRGB_HEL; + } + + memcpy(lpD3DFDR, &lpD3DFRD3, sizeof(D3DFINDDEVICERESULT3)); + } else { + Logger::err("D3D3Interface::FindDevice: Unhandled matching type"); + return DDERR_NOTFOUND; + } + + return D3D_OK; + } + +} \ No newline at end of file diff --git a/src/ddraw/d3d3/d3d3_interface.h b/src/ddraw/d3d3/d3d3_interface.h new file mode 100644 index 00000000000..b74e852696d --- /dev/null +++ b/src/ddraw/d3d3/d3d3_interface.h @@ -0,0 +1,69 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_options.h" +#include "../ddraw_util.h" +#include "../ddraw_format.h" + +#include "../ddraw_common_interface.h" +#include "../d3d_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +namespace dxvk { + + /** + * \brief D3D3 interface implementation + */ + class D3D3Interface final : public DDrawWrappedObject { + + public: + D3D3Interface( + DDrawCommonInterface* commonIntf, + D3DCommonInterface* commonD3DIntf, + Com&& d3d3Intf, + IUnknown* pParent); + + ~D3D3Interface(); + + ULONG STDMETHODCALLTYPE AddRef(); + + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE Initialize(REFIID riid); + + HRESULT STDMETHODCALLTYPE EnumDevices(LPD3DENUMDEVICESCALLBACK lpEnumDevicesCallback, LPVOID lpUserArg); + + HRESULT STDMETHODCALLTYPE CreateLight(LPDIRECT3DLIGHT *lplpDirect3DLight, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateMaterial(LPDIRECT3DMATERIAL *lplpDirect3DMaterial, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateViewport(LPDIRECT3DVIEWPORT *lplpD3DViewport, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE FindDevice(D3DFINDDEVICESEARCH *lpD3DFDS, D3DFINDDEVICERESULT *lpD3DFDR); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + D3DCommonInterface* GetCommonD3DInterface() const { + return m_commonD3DIntf.ptr(); + } + + private: + + static uint32_t s_intfCount; + uint32_t m_intfCount = 0; + + Com m_bridge; + + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_commonD3DIntf; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d3/d3d3_material.cpp b/src/ddraw/d3d3/d3d3_material.cpp new file mode 100644 index 00000000000..b754dfed548 --- /dev/null +++ b/src/ddraw/d3d3/d3d3_material.cpp @@ -0,0 +1,112 @@ +#include "d3d3_material.h" + +#include "d3d3_device.h" +#include "d3d3_interface.h" +#include "d3d3_viewport.h" + +#include "../ddraw/ddraw_interface.h" + +namespace dxvk { + + uint32_t D3D3Material::s_materialCount = 0; + + D3D3Material::D3D3Material( + Com&& proxyMaterial, + D3D3Interface* pParent, + D3DMATERIALHANDLE handle) + : DDrawWrappedObject(pParent, std::move(proxyMaterial), nullptr) { + m_commonMaterial = new D3DCommonMaterial(handle); + + m_materialCount = ++s_materialCount; + + Logger::debug(str::format("D3D3Material: Created a new material nr. [[1-", m_materialCount, "]]")); + } + + D3D3Material::~D3D3Material() { + m_parent->GetCommonD3DInterface()->ReleaseMaterialHandle(m_commonMaterial->GetMaterialHandle()); + + Logger::debug(str::format("D3D3Material: Material nr. [[1-", m_materialCount, "]] bites the dust")); + } + + // Docs state: "Returns DDERR_ALREADYINITIALIZED because the + // Direct3DMaterial object is initialized when it is created." + HRESULT STDMETHODCALLTYPE D3D3Material::Initialize(LPDIRECT3D lpDirect3D) { + Logger::debug(">>> D3D3Material::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE D3D3Material::SetMaterial(D3DMATERIAL *data) { + Logger::debug(">>> D3D3Material::SetMaterial"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + d3d9::D3DMATERIAL9* material9 = m_commonMaterial->GetD3D9Material(); + + material9->Diffuse = data->dcvDiffuse; + material9->Ambient = data->dcvAmbient; + material9->Specular = data->dcvSpecular; + material9->Emissive = data->dcvEmissive; + material9->Power = data->dvPower; + + D3DMATERIALHANDLE handle = m_commonMaterial->GetMaterialHandle(); + + Logger::debug(str::format(">>> D3D3Material::SetMaterial: Updated material nr. ", handle)); + Logger::debug(str::format(" Diffuse: ", material9->Diffuse.r, " ", material9->Diffuse.g, " ", material9->Diffuse.b)); + Logger::debug(str::format(" Ambient: ", material9->Ambient.r, " ", material9->Ambient.g, " ", material9->Ambient.b)); + Logger::debug(str::format(" Specular: ", material9->Specular.r, " ", material9->Specular.g, " ", material9->Specular.b)); + Logger::debug(str::format(" Emissive: ", material9->Emissive.r, " ", material9->Emissive.g, " ", material9->Emissive.b)); + Logger::debug(str::format(" Power: ", material9->Power)); + + // Update the D3D9 material directly if it's actively being used + D3D3Device* device3 = m_parent->GetCommonInterface()->GetD3D3Device(); + if (likely(device3 != nullptr)) { + D3DMATERIALHANDLE currentHandle = device3->GetCurrentMaterialHandle(); + if (currentHandle == handle) { + Logger::debug(str::format("D3D3Material::SetMaterial: Applying material nr. ", handle, " to D3D9")); + device3->GetD3D9()->SetMaterial(material9); + } + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Material::GetMaterial(D3DMATERIAL *data) { + Logger::debug(">>> D3D3Material::GetMaterial"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + d3d9::D3DMATERIAL9* material9 = m_commonMaterial->GetD3D9Material(); + + data->dcvDiffuse = material9->Diffuse; + data->dcvAmbient = material9->Ambient; + data->dcvSpecular = material9->Specular; + data->dcvEmissive = material9->Emissive; + data->dvPower = material9->Power; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Material::GetHandle(IDirect3DDevice *device, D3DMATERIALHANDLE *handle) { + Logger::debug(">>> D3D3Material::GetHandle"); + + if(unlikely(device == nullptr || handle == nullptr)) + return DDERR_INVALIDPARAMS; + + *handle = m_commonMaterial->GetMaterialHandle(); + + return D3D_OK; + } + + // Docs state: "Not currently implemented." + HRESULT STDMETHODCALLTYPE D3D3Material::Reserve() { + return DDERR_UNSUPPORTED; + } + + // Docs state: "Not currently implemented." + HRESULT STDMETHODCALLTYPE D3D3Material::Unreserve() { + return DDERR_UNSUPPORTED; + } + +} diff --git a/src/ddraw/d3d3/d3d3_material.h b/src/ddraw/d3d3/d3d3_material.h new file mode 100644 index 00000000000..222a49acc72 --- /dev/null +++ b/src/ddraw/d3d3/d3d3_material.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../d3d_common_material.h" + +namespace dxvk { + + class D3D3Interface; + + class D3D3Material final : public DDrawWrappedObject { + + public: + + D3D3Material( + Com&& proxyMaterial, + D3D3Interface* pParent, + D3DMATERIALHANDLE handle); + + ~D3D3Material(); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECT3D lpDirect3D); + + HRESULT STDMETHODCALLTYPE SetMaterial(D3DMATERIAL *data); + + HRESULT STDMETHODCALLTYPE GetMaterial(D3DMATERIAL *data); + + HRESULT STDMETHODCALLTYPE GetHandle(IDirect3DDevice *device, D3DMATERIALHANDLE *handle); + + HRESULT STDMETHODCALLTYPE Reserve(); + + HRESULT STDMETHODCALLTYPE Unreserve(); + + D3DCommonMaterial* GetCommonMaterial() const { + return m_commonMaterial.ptr(); + } + + private: + + static uint32_t s_materialCount; + uint32_t m_materialCount = 0; + + Com m_commonMaterial; + + }; + +} diff --git a/src/ddraw/d3d3/d3d3_texture.cpp b/src/ddraw/d3d3/d3d3_texture.cpp new file mode 100644 index 00000000000..c5866734c77 --- /dev/null +++ b/src/ddraw/d3d3/d3d3_texture.cpp @@ -0,0 +1,141 @@ +#include "d3d3_texture.h" + +#include "d3d3_device.h" + +#include "../ddraw/ddraw_surface.h" + +namespace dxvk { + + uint32_t D3D3Texture::s_texCount = 0; + + D3D3Texture::D3D3Texture( + Com&& proxyTexture, + DDrawSurface* pParent, + D3DTEXTUREHANDLE handle) + : DDrawWrappedObject(pParent, std::move(proxyTexture), nullptr) { + m_commonTex = new D3DCommonTexture(m_parent->GetCommonSurface(), handle); + + m_texCount = ++s_texCount; + + Logger::debug(str::format("D3D3Texture: Created a new texture nr. [[1-", m_texCount, "]]")); + } + + D3D3Texture::~D3D3Texture() { + m_parent->GetCommonInterface()->ReleaseTextureHandle(m_commonTex->GetTextureHandle()); + + Logger::debug(str::format("D3D3Texture: Texture nr. [[1-", m_texCount, "]] bites the dust")); + } + + // Interlocked refcount with the parent IDirectDrawSurface + ULONG STDMETHODCALLTYPE D3D3Texture::AddRef() { + return m_parent->AddRef(); + } + + // Interlocked refcount with the parent IDirectDrawSurface + ULONG STDMETHODCALLTYPE D3D3Texture::Release() { + return m_parent->Release(); + } + + HRESULT STDMETHODCALLTYPE D3D3Texture::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> D3D3Texture::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(riid == __uuidof(IDirect3DTexture2))) { + Logger::debug("D3D3Texture::QueryInterface: Query for IDirect3DTexture"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawGammaControl))) { + Logger::debug("D3D3Texture::QueryInterface: Query for IDirectDrawGammaControl"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawColorControl))) { + Logger::debug("D3D3Texture::QueryInterface: Query for IDirectDrawColorControl"); + return E_NOINTERFACE; + } + if (unlikely(riid == __uuidof(IUnknown) + || riid == __uuidof(IDirectDrawSurface))) { + Logger::debug("D3D3Texture::QueryInterface: Query for IDirectDrawSurface"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface2))) { + Logger::debug("D3D3Texture::QueryInterface: Query for IDirectDrawSurface2"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface3))) { + Logger::debug("D3D3Texture::QueryInterface: Query for IDirectDrawSurface3"); + return m_parent->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // Frogger gets texture handles from IDirect3DTexture objects and uses them in calls on a IDirect3DDevice2 + HRESULT STDMETHODCALLTYPE D3D3Texture::GetHandle(LPDIRECT3DDEVICE lpDirect3DDevice, LPD3DTEXTUREHANDLE lpHandle) { + Logger::debug(">>> D3D3Texture::GetHandle"); + + if(unlikely(lpDirect3DDevice == nullptr || lpHandle == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpHandle = m_commonTex->GetTextureHandle(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Texture::PaletteChanged(DWORD dwStart, DWORD dwCount) { + Logger::warn("<<< D3D3Texture::PaletteChanged: Proxy"); + return m_proxy->PaletteChanged(dwStart, dwCount); + } + + HRESULT STDMETHODCALLTYPE D3D3Texture::Load(LPDIRECT3DTEXTURE lpD3DTexture) { + Logger::debug("<<< D3D3Texture::Load: Proxy"); + + Com d3d3Texture = static_cast(lpD3DTexture); + + HRESULT hr = m_proxy->Load(d3d3Texture->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + // Update the cached parent surface desc + DDSURFACEDESC desc; + desc.dwSize = sizeof(DDSURFACEDESC); + HRESULT hrDesc = m_parent->GetProxied()->GetSurfaceDesc(&desc); + + if (unlikely(FAILED(hrDesc))) { + Logger::err("D3D3Texture::Load: Failed to retrieve updated surface desc"); + } else { + m_parent->GetCommonSurface()->SetDesc(desc); + } + + m_parent->GetCommonSurface()->DirtyMipMaps(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D3Texture::Initialize(LPDIRECT3DDEVICE lpDirect3DDevice, LPDIRECTDRAWSURFACE lpDDSurface) { + Logger::debug("<<< D3D3Texture::Initialize: Proxy"); + + if(unlikely(lpDirect3DDevice == nullptr || lpDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D3Device* d3d3Device = static_cast(lpDirect3DDevice); + DDrawSurface* ddrawSurface = static_cast(lpDDSurface); + return m_proxy->Initialize(d3d3Device->GetProxied(), ddrawSurface->GetProxied()); + } + + HRESULT STDMETHODCALLTYPE D3D3Texture::Unload() { + Logger::debug("<<< D3D3Texture::Unload: Proxy"); + return m_proxy->Unload(); + } + +} diff --git a/src/ddraw/d3d3/d3d3_texture.h b/src/ddraw/d3d3/d3d3_texture.h new file mode 100644 index 00000000000..c8bb8d96965 --- /dev/null +++ b/src/ddraw/d3d3/d3d3_texture.h @@ -0,0 +1,52 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../d3d_common_texture.h" + +namespace dxvk { + + class DDrawSurface; + + class D3D3Texture final : public DDrawWrappedObject { + + public: + + D3D3Texture( + Com&& proxyTexture, + DDrawSurface* pParent, + D3DTEXTUREHANDLE handle); + + ~D3D3Texture(); + + ULONG STDMETHODCALLTYPE AddRef(); + + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE GetHandle(LPDIRECT3DDEVICE lpDirect3DDevice, LPD3DTEXTUREHANDLE lpHandle); + + HRESULT STDMETHODCALLTYPE PaletteChanged(DWORD dwStart, DWORD dwCount); + + HRESULT STDMETHODCALLTYPE Load(LPDIRECT3DTEXTURE lpD3DTexture); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECT3DDEVICE lpDirect3DDevice, LPDIRECTDRAWSURFACE lpDDSurface); + + HRESULT STDMETHODCALLTYPE Unload(); + + D3DCommonTexture* GetCommonTexture() const { + return m_commonTex.ptr(); + } + + private: + + static uint32_t s_texCount; + uint32_t m_texCount = 0; + + Com m_commonTex; + + }; + +} diff --git a/src/ddraw/d3d3/d3d3_viewport.cpp b/src/ddraw/d3d3/d3d3_viewport.cpp new file mode 100644 index 00000000000..350c20bf4ed --- /dev/null +++ b/src/ddraw/d3d3/d3d3_viewport.cpp @@ -0,0 +1,438 @@ +#include "d3d3_viewport.h" + +#include "d3d3_device.h" + +#include "../d3d_light.h" +#include "../d3d_common_material.h" + +#include "../ddraw/ddraw_surface.h" + +#include "../d3d5/d3d5_viewport.h" +#include "../d3d6/d3d6_viewport.h" + +#include +#include + +namespace dxvk { + + uint32_t D3D3Viewport::s_viewportCount = 0; + + D3D3Viewport::D3D3Viewport( + D3DCommonViewport* commonViewport, + Com&& proxyViewport, + D3D3Interface* pParent) + : DDrawWrappedObject(pParent, std::move(proxyViewport), nullptr) + , m_commonViewport ( commonViewport ) { + + if (m_commonViewport == nullptr) + m_commonViewport = new D3DCommonViewport(m_parent->GetCommonD3DInterface()); + + m_commonViewport->SetD3D3Viewport(this); + + m_viewportCount = ++s_viewportCount; + + Logger::debug(str::format("D3D3Viewport: Created a new viewport nr. [[1-", m_viewportCount, "]]")); + } + + D3D3Viewport::~D3D3Viewport() { + std::vector>& lights = m_commonViewport->GetLights(); + + // Dissasociate every bound light from this viewport + for (auto light : lights) { + light->SetViewport3(nullptr); + } + + m_commonViewport->SetD3D3Viewport(nullptr); + + Logger::debug(str::format("D3D3Viewport: Viewport nr. [[1-", m_viewportCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> D3D3Viewport::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(riid == __uuidof(IDirect3DViewport2))) { + if (m_commonViewport->GetD3D5Viewport() != nullptr) { + Logger::debug("D3D3Viewport::QueryInterface: Query for existing IDirect3DViewport2"); + return m_commonViewport->GetD3D5Viewport()->QueryInterface(riid, ppvObject); + } + + Logger::debug("D3D3Viewport::QueryInterface: Query for IDirect3DViewport2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new D3D5Viewport(m_commonViewport.ptr(), std::move(ppvProxyObject), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirect3DViewport3))) { + if (m_commonViewport->GetD3D6Viewport() != nullptr) { + Logger::debug("D3D3Viewport::QueryInterface: Query for existing IDirect3DViewport3"); + return m_commonViewport->GetD3D6Viewport()->QueryInterface(riid, ppvObject); + } + + Logger::debug("D3D3Viewport::QueryInterface: Query for IDirect3DViewport3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new D3D6Viewport(m_commonViewport.ptr(), std::move(ppvProxyObject), nullptr)); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // Docs state: "The IDirect3DViewport2::Initialize method is not implemented." + HRESULT STDMETHODCALLTYPE D3D3Viewport::Initialize(LPDIRECT3D lpDirect3D) { + Logger::debug(">>> D3D3Viewport::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::GetViewport(D3DVIEWPORT *data) { + Logger::debug(">>> D3D3Viewport::GetViewport"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->IsViewportSet())) + return D3DERR_VIEWPORTDATANOTSET; + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + data->dwX = viewport9->X; + data->dwY = viewport9->Y; + data->dwWidth = viewport9->Width; + data->dwHeight = viewport9->Height; + data->dvMinZ = viewport9->MinZ; + data->dvMaxZ = viewport9->MaxZ; + + data->dvMaxX = 1.0f; + data->dvMaxY = 1.0f; + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + data->dvScaleX = legacyScale->x * (float)data->dwWidth / 2.0f; + data->dvScaleY = legacyScale->y * (float)data->dwHeight / 2.0f; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::SetViewport(D3DVIEWPORT *data) { + Logger::debug(">>> D3D3Viewport::SetViewport"); + + HRESULT hr = m_proxy->SetViewport(data); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + // TODO: Check viewport dimensions against the currently set RT, + // and perform some sanity checks (positive, non-zero dimensions) + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + // The docs state: "The method ignores the values in the dvMaxX, dvMaxY, + // dvMinZ, and dvMaxZ members.", which appears correct. + viewport9->X = data->dwX; + viewport9->Y = data->dwY; + viewport9->Width = data->dwWidth; + viewport9->Height = data->dwHeight; + viewport9->MinZ = 0.0f; + viewport9->MaxZ = 1.0f; + + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + legacyScale->x = 2.0f * data->dvScaleX / (float)data->dwWidth; + legacyScale->y = 2.0f * data->dvScaleY / (float)data->dwHeight; + legacyScale->z = 1.0f; + D3DVECTOR* legacyClip = m_commonViewport->GetLegacyClip(); + legacyClip->x = 0.0f; + legacyClip->y = 0.0f; + legacyClip->z = 0.0f; + + m_commonViewport->MarkViewportAsSet(); + + if (m_commonViewport->IsCurrentViewport()) + ApplyViewport(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::TransformVertices(DWORD vertex_count, D3DTRANSFORMDATA *data, DWORD flags, DWORD *offscreen) { + Logger::warn("<<< D3D3Viewport::TransformVertices: Proxy"); + return m_proxy->TransformVertices(vertex_count, data, flags, offscreen); + } + + // Docs state: "The IDirect3DViewport::LightElements method is not currently implemented." + HRESULT STDMETHODCALLTYPE D3D3Viewport::LightElements(DWORD element_count, D3DLIGHTDATA *data) { + Logger::warn(">>> D3D3Viewport::LightElements"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::SetBackground(D3DMATERIALHANDLE hMat) { + // Workaround: Revenant sets the background on a IDirect3DViewport viewport + if (unlikely(m_commonViewport->GetD3D6Viewport() != nullptr)) { + Logger::debug(">>> D3D3Viewport::SetBackground"); + return m_commonViewport->GetD3D6Viewport()->SetBackground(hMat); + } + + Logger::debug(">>> D3D3Viewport::SetBackground"); + + if (unlikely(m_commonViewport->GetMaterialHandle() == hMat)) + return D3D_OK; + + D3DCommonMaterial* commonMaterial = m_commonViewport->GetCommonD3DInterface()->GetCommonMaterialFromHandle(hMat); + + if (unlikely(commonMaterial == nullptr)) + return DDERR_INVALIDPARAMS; + + m_commonViewport->MarkMaterialAsSet(); + + // Cache only the set material handle, as its color can + // change after it is set (get it on Clear directly) + m_commonViewport->SetMaterialHandle(hMat); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::GetBackground(D3DMATERIALHANDLE *material, BOOL *valid) { + Logger::debug(">>> D3D3Viewport::GetBackground"); + + if (unlikely(material == nullptr || valid == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(m_commonViewport->IsMaterialSet())) + *material = m_commonViewport->GetMaterialHandle(); + *valid = m_commonViewport->IsMaterialSet(); + + return D3D_OK; + } + + // One could speculate this was meant to set a z-buffer depth value + // to be used during clears, perhaps, similarly to SetBackground(), + // however it has not seen any practical use in the wild + HRESULT STDMETHODCALLTYPE D3D3Viewport::SetBackgroundDepth(IDirectDrawSurface *surface) { + Logger::warn("!!! D3D3Viewport::SetBackgroundDepth: Stub"); + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::GetBackgroundDepth(IDirectDrawSurface **surface, BOOL *valid) { + Logger::warn("!!! D3D3Viewport::SetBackgroundDepth: Stub"); + + if (unlikely(surface == nullptr || valid == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(surface); + + *valid = FALSE; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::Clear(DWORD count, D3DRECT *rects, DWORD flags) { + Logger::debug("<<< D3D3Viewport::Clear: Proxy"); + + // Fast skip + if (unlikely(!count && rects)) + return D3D_OK; + + HRESULT hr = m_proxy->Clear(count, rects, flags); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonViewport->GetD3D9Device(); + + // Temporarily activate this viewport in order to clear it + d3d9::D3DVIEWPORT9 currentViewport9; + if (!m_commonViewport->IsCurrentViewport()) { + D3D3Viewport* currentViewport = m_commonViewport->GetCurrentD3D3Viewport(); + if (currentViewport != nullptr) { + currentViewport9 = *currentViewport->GetCommonViewport()->GetD3D9Viewport(); + } else { + d3d9Device->GetViewport(¤tViewport9); + } + d3d9Device->SetViewport(m_commonViewport->GetD3D9Viewport()); + } + + static constexpr D3DCOLOR defaultColor = D3DCOLOR_RGBA(0, 0, 0, 0); + D3DMATERIALHANDLE handle = m_commonViewport->GetMaterialHandle(); + D3DCommonMaterial* commonMaterial = m_commonViewport->GetCommonD3DInterface()->GetCommonMaterialFromHandle(handle); + D3DCOLOR clearColor = commonMaterial != nullptr ? commonMaterial->GetMaterialColor() : defaultColor; + + HRESULT hr9 = d3d9Device->Clear(count, rects, flags, clearColor, 1.0f, 0u); + if (unlikely(FAILED(hr9))) + Logger::err("D3D3Viewport::Clear: Failed D3D9 Clear call"); + + // Restore the previously active viewport + if (!m_commonViewport->IsCurrentViewport()) { + d3d9Device->SetViewport(¤tViewport9); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::AddLight(IDirect3DLight *light) { + Logger::debug(">>> D3D3Viewport::AddLight"); + + if (unlikely(light == nullptr)) + return DDERR_INVALIDPARAMS; + + D3DLight* d3dLight = reinterpret_cast(light); + + if (unlikely(d3dLight->HasViewport())) + return D3DERR_LIGHTHASVIEWPORT; + + if (m_commonViewport->HasDevice()) { + Logger::debug("D3D3Viewport::AddLight: Enabling device legacy light model"); + m_commonViewport->EnableLegacyLights(d3dLight->IsD3DLight2()); + } + + std::vector>& lights = m_commonViewport->GetLights(); + // No need to check if the light is already attached, since + // if that's the case it will have a set viewport above + lights.push_back(d3dLight); + d3dLight->SetViewport3(this); + + if (m_commonViewport->HasDevice() && m_commonViewport->IsCurrentViewport()) + ApplyAndActivateLight(d3dLight->GetIndex(), d3dLight); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::DeleteLight(IDirect3DLight *light) { + Logger::debug(">>> D3D3Viewport::DeleteLight"); + + if (unlikely(light == nullptr)) + return DDERR_INVALIDPARAMS; + + D3DLight* d3dLight = reinterpret_cast(light); + + if (unlikely(!d3dLight->HasViewport())) + return DDERR_INVALIDPARAMS; + + std::vector>& lights = m_commonViewport->GetLights(); + + auto it = std::find(lights.begin(), lights.end(), d3dLight); + if (likely(it != lights.end())) { + const DWORD lightIndex = d3dLight->GetIndex(); + if (m_commonViewport->HasDevice() && m_commonViewport->IsCurrentViewport() && d3dLight->IsActive()) { + Logger::debug(str::format("D3D3Viewport: Disabling light nr. ", lightIndex)); + m_commonViewport->GetD3D9Device()->LightEnable(lightIndex, FALSE); + } + lights.erase(it); + d3dLight->SetViewport3(nullptr); + } else { + Logger::warn("D3D3Viewport::DeleteLight: Light not found"); + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D3Viewport::NextLight(IDirect3DLight *lpDirect3DLight, IDirect3DLight **lplpDirect3DLight, DWORD flags) { + Logger::debug(">>> D3D3Viewport::NextLight"); + + if (unlikely(lplpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DLight); + + std::vector>& lights = m_commonViewport->GetLights(); + + if (flags & D3DNEXT_HEAD) { + if (likely(lights.size() > 0)) + *lplpDirect3DLight = lights.front().ref(); + } else if (flags & D3DNEXT_NEXT) { + if (unlikely(lpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(lights.size() > 0)) + Logger::warn("D3D3Viewport::NextLight: Unimplemented D3DNEXT_NEXT flag"); + } else if (flags & D3DNEXT_TAIL) { + if (likely(lights.size() > 0)) + *lplpDirect3DLight = lights.back().ref(); + } + + return D3D_OK; + } + + HRESULT D3D3Viewport::ApplyViewport() { + if (!m_commonViewport->IsViewportSet()) + return D3D_OK; + + Logger::debug("D3D3Viewport: Applying viewport to D3D9"); + + HRESULT hr = m_commonViewport->GetD3D9Device()->SetViewport(m_commonViewport->GetD3D9Viewport()); + if(unlikely(FAILED(hr))) + Logger::err("D3D3Viewport: Failed to set the D3D9 viewport"); + + return hr; + } + + HRESULT D3D3Viewport::ApplyAndActivateLights() { + std::vector>& lights = m_commonViewport->GetLights(); + + if (!lights.size()) + return D3D_OK; + + Logger::debug("D3D3Viewport: Applying lights to D3D9"); + + for (auto light: lights) + ApplyAndActivateLight(light->GetIndex(), light.ptr()); + + return D3D_OK; + } + + HRESULT D3D3Viewport::ApplyAndActivateLight(DWORD index, D3DLight* light) { + d3d9::IDirect3DDevice9* d3d9Device = m_commonViewport->GetD3D9Device(); + + HRESULT hr = d3d9Device->SetLight(index, light->GetD3D9Light()); + if (unlikely(FAILED(hr))) { + Logger::err("D3D3Viewport: Failed D3D9 SetLight call"); + } else { + HRESULT hrLE; + if (light->IsActive()) { + Logger::debug(str::format("D3D3Viewport: Enabling light nr. ", index)); + hrLE = d3d9Device->LightEnable(index, TRUE); + if (unlikely(FAILED(hrLE))) + Logger::err("D3D3Viewport: Failed D3D9 LightEnable call (TRUE)"); + } else { + Logger::debug(str::format("D3D3Viewport: Disabling light nr. ", index)); + hrLE = d3d9Device->LightEnable(index, FALSE); + if (unlikely(FAILED(hrLE))) + Logger::err("D3D3Viewport: Failed D3D9 LightEnable call (FALSE)"); + } + } + + return hr; + } + +} diff --git a/src/ddraw/d3d3/d3d3_viewport.h b/src/ddraw/d3d3/d3d3_viewport.h new file mode 100644 index 00000000000..7d4161ccec8 --- /dev/null +++ b/src/ddraw/d3d3/d3d3_viewport.h @@ -0,0 +1,72 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../d3d_common_viewport.h" + +#include "d3d3_interface.h" + +namespace dxvk { + + class D3DLight; + + class D3D3Viewport final : public DDrawWrappedObject { + + public: + + D3D3Viewport( + D3DCommonViewport* commonViewport, + Com&& proxyViewport, + D3D3Interface* pParent); + + ~D3D3Viewport(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECT3D lpDirect3D); + + HRESULT STDMETHODCALLTYPE GetViewport(D3DVIEWPORT *data); + + HRESULT STDMETHODCALLTYPE SetViewport(D3DVIEWPORT *data); + + HRESULT STDMETHODCALLTYPE TransformVertices(DWORD vertex_count, D3DTRANSFORMDATA *data, DWORD flags, DWORD *offscreen); + + HRESULT STDMETHODCALLTYPE LightElements(DWORD element_count, D3DLIGHTDATA *data); + + HRESULT STDMETHODCALLTYPE SetBackground(D3DMATERIALHANDLE hMat); + + HRESULT STDMETHODCALLTYPE GetBackground(D3DMATERIALHANDLE *material, BOOL *valid); + + HRESULT STDMETHODCALLTYPE SetBackgroundDepth(IDirectDrawSurface *surface); + + HRESULT STDMETHODCALLTYPE GetBackgroundDepth(IDirectDrawSurface **surface, BOOL *valid); + + HRESULT STDMETHODCALLTYPE Clear(DWORD count, D3DRECT *rects, DWORD flags); + + HRESULT STDMETHODCALLTYPE AddLight(IDirect3DLight *light); + + HRESULT STDMETHODCALLTYPE DeleteLight(IDirect3DLight *light); + + HRESULT STDMETHODCALLTYPE NextLight(IDirect3DLight *lpDirect3DLight, IDirect3DLight **lplpDirect3DLight, DWORD flags); + + HRESULT ApplyViewport(); + + HRESULT ApplyAndActivateLights(); + + HRESULT ApplyAndActivateLight(DWORD index, D3DLight* light); + + D3DCommonViewport* GetCommonViewport() const { + return m_commonViewport.ptr(); + } + + private: + + static uint32_t s_viewportCount; + uint32_t m_viewportCount = 0; + + Com m_commonViewport; + + }; + +} diff --git a/src/ddraw/d3d5/d3d5_device.cpp b/src/ddraw/d3d5/d3d5_device.cpp new file mode 100644 index 00000000000..fefae193d76 --- /dev/null +++ b/src/ddraw/d3d5/d3d5_device.cpp @@ -0,0 +1,1555 @@ +#include "d3d5_device.h" + +#include "../ddraw_util.h" + +#include "../d3d_common_texture.h" + +#include "../d3d3/d3d3_device.h" + +#include "../ddraw/ddraw_surface.h" +#include "../ddraw2/ddraw2_interface.h" + +#include +#include +#include "../../util/util_bit.h" + +// Supress warnings about D3DRENDERSTATE_ALPHABLENDENABLE_OLD +// not being in the shipped D3D5 enum (thanks a lot, MS) +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wswitch" +#endif + +namespace dxvk { + + uint32_t D3D5Device::s_deviceCount = 0; + + D3D5Device::D3D5Device( + Com&& d3d5DeviceProxy, + D3D5Interface* pParent, + D3DDEVICEDESC2 Desc, + GUID deviceGUID, + d3d9::D3DPRESENT_PARAMETERS Params9, + Com&& pDevice9, + DDrawSurface* pSurface, + DWORD CreationFlags9) + : DDrawWrappedObject(pParent, std::move(d3d5DeviceProxy), std::move(pDevice9)) + , m_commonIntf ( pParent->GetCommonInterface() ) + , m_creationFlags9 ( CreationFlags9 ) + , m_multithread ( CreationFlags9 & D3DCREATE_MULTITHREADED ) + , m_params9 ( Params9 ) + , m_desc ( Desc ) + , m_deviceGUID ( deviceGUID ) + , m_rt ( pSurface ) { + // Get the bridge interface to D3D9 + if (unlikely(FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8Bridge), reinterpret_cast(&m_bridge))))) { + throw DxvkError("D3D5Device: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + m_totalMemory = m_bridge->DetermineInitialTextureMemory(); + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->emulateFSAA == FSAAEmulation::Forced)) { + Logger::warn("D3D5Device: Force enabling AA"); + m_d3d9->SetRenderState(d3d9::D3DRS_MULTISAMPLEANTIALIAS, TRUE); + } + + // The default value of D3DRENDERSTATE_TEXTUREMAPBLEND in D3D5 is D3DTBLEND_MODULATE + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + + m_deviceCount = ++s_deviceCount; + + Logger::debug(str::format("D3D5Device: Created a new device nr. ((2-", m_deviceCount, "))")); + } + + D3D5Device::~D3D5Device() { + // Dissasociate every bound viewport from this device + for (auto viewport : m_viewports) { + viewport->GetCommonViewport()->SetD3D5Device(nullptr); + } + + // Clear the common interface device pointer if it points to this device + if (m_commonIntf->GetD3D5Device() == this) + m_commonIntf->SetD3D5Device(nullptr); + + Logger::debug(str::format("D3D5Device: Device nr. ((2-", m_deviceCount, ")) bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D5Device::QueryInterface(REFIID riid, void** ppvObject) { + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(riid == __uuidof(IDirect3DDevice))) { + if (m_commonIntf->GetD3D3Device() != nullptr) { + Logger::debug("D3D3Device::QueryInterface: Query for existing IDirect3DDevice"); + return m_commonIntf->GetD3D3Device()->QueryInterface(riid, ppvObject); + } + + Logger::debug("D3D5Device::QueryInterface: Query for IDirect3DDevice"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // Reuse the existing D3D9 device in situations where games want + // to get access only to D3D3 execute buffers on a D3D5 device + Com device9 = m_d3d9.ptr(); + *ppvObject = ref(new D3D3Device(std::move(ppvProxyObject), m_rt.ptr(), GetD3D3Caps(d3dOptions), + m_deviceGUID, m_params9, std::move(device9), m_creationFlags9)); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetCaps(D3DDEVICEDESC *hal_desc, D3DDEVICEDESC *hel_desc) { + Logger::debug(">>> D3D5Device::GetCaps"); + + if (unlikely(hal_desc == nullptr || hel_desc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!IsValidD3DDeviceDescSize(hal_desc->dwSize) + || !IsValidD3DDeviceDescSize(hel_desc->dwSize))) + return DDERR_INVALIDPARAMS; + + D3DDEVICEDESC2 desc_HAL = m_desc; + D3DDEVICEDESC2 desc_HEL = m_desc; + + if (m_deviceGUID == IID_IDirect3DRGBDevice) { + desc_HAL.dwFlags = 0; + desc_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + desc_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL; + desc_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL; + desc_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + desc_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + } else if (m_deviceGUID == IID_IDirect3DHALDevice) { + desc_HEL.dcmColorModel = 0; + desc_HEL.dwDevCaps &= ~D3DDEVCAPS_HWTRANSFORMANDLIGHT + & ~D3DDEVCAPS_DRAWPRIMITIVES2 + & ~D3DDEVCAPS_DRAWPRIMITIVES2EX; + } else { + Logger::warn("D3D5Device::GetCaps: Unhandled device type"); + } + + memcpy(hal_desc, &desc_HAL, hal_desc->dwSize); + memcpy(hel_desc, &desc_HEL, hel_desc->dwSize); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::SwapTextureHandles(IDirect3DTexture2 *tex1, IDirect3DTexture2 *tex2) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::SwapTextureHandles"); + + D3D5Texture* texture1 = static_cast(tex1); + D3D5Texture* texture2 = static_cast(tex2); + + D3DCommonTexture* commonTex1 = texture1->GetCommonTexture(); + D3DCommonTexture* commonTex2 = texture2->GetCommonTexture(); + + const D3DTEXTUREHANDLE handle1 = commonTex1->GetTextureHandle(); + const D3DTEXTUREHANDLE handle2 = commonTex2->GetTextureHandle(); + + m_parent->GetCommonInterface()->ReleaseTextureHandle(handle1); + m_parent->GetCommonInterface()->ReleaseTextureHandle(handle2); + + commonTex1->SetTextureHandle(handle2); + commonTex2->SetTextureHandle(handle1); + + m_commonIntf->EmplaceTexture(commonTex1, handle2); + m_commonIntf->EmplaceTexture(commonTex2, handle1); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetStats(D3DSTATS *stats) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::GetStats"); + + if (unlikely(stats == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(stats->dwSize != sizeof(D3DSTATS))) + return DDERR_INVALIDPARAMS; + + D3DSTATS newStats = { }; + + if (likely(m_commonIntf->GetD3D3Device() != nullptr)) + newStats = m_commonIntf->GetD3D3Device()->GetStatsInternal(); + + const DWORD dwSize = stats->dwSize; + + *stats = newStats; + stats->dwSize = dwSize; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::AddViewport(IDirect3DViewport2 *viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::AddViewport"); + + if (unlikely(viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D5Viewport* d3d5Viewport = static_cast(viewport); + HRESULT hr = m_proxy->AddViewport(d3d5Viewport->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + AddViewportInternal(viewport); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::DeleteViewport(IDirect3DViewport2 *viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::DeleteViewport"); + + if (unlikely(viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D5Viewport* d3d5Viewport = static_cast(viewport); + HRESULT hr = m_proxy->DeleteViewport(d3d5Viewport->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + DeleteViewportInternal(viewport); + + // Clear the current viewport if it is deleted from the device + if (m_currentViewport.ptr() == d3d5Viewport) + m_currentViewport = nullptr; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::NextViewport(IDirect3DViewport2 *lpDirect3DViewport, IDirect3DViewport2 **lplpAnotherViewport, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::NextViewport"); + + if (unlikely(lplpAnotherViewport == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpAnotherViewport); + + if (flags & D3DNEXT_HEAD) { + if (likely(m_viewports.size() > 0)) + *lplpAnotherViewport = m_viewports.front().ref(); + } else if (flags & D3DNEXT_NEXT) { + if (unlikely(lpDirect3DViewport == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(m_viewports.size() > 0)) + Logger::warn("D3D5Device::NextViewport: Unimplemented D3DNEXT_NEXT flag"); + } else if (flags & D3DNEXT_TAIL) { + if (likely(m_viewports.size() > 0)) + *lplpAnotherViewport = m_viewports.back().ref(); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::EnumTextureFormats(LPD3DENUMTEXTUREFORMATSCALLBACK cb, void *ctx) { + Logger::debug(">>> D3D5Device::EnumTextureFormats"); + + if (unlikely(cb == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // This is a DDSURFACEDESC in D3D5, because why not... + DDSURFACEDESC textureFormat = { }; + textureFormat.dwSize = sizeof(DDSURFACEDESC); + textureFormat.dwFlags = DDSD_CAPS | DDSD_PIXELFORMAT; + textureFormat.ddsCaps.dwCaps = DDSCAPS_TEXTURE; + + // Note: The list of formats exposed in D3D5 is restricted to the below + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_X1R5G5B5); + HRESULT hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_A1R5G5B5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + // D3DFMT_X4R4G4B4 is not supported by D3D5 + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_A4R4G4B4); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_R5G6B5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_X8R8G8B8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_A8R8G8B8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + // Not supported in D3D9, but some games need + // it to be advertised (for offscreen plain surfaces?) + if (unlikely(d3dOptions->supportR3G3B2)) { + textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_R3G3B2); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + } + + // Not supported in D3D9, but some games may use it + /*textureFormat.ddpfPixelFormat = GetTextureFormat(d3d9::D3DFMT_P8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK;*/ + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::BeginScene() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::BeginScene"); + + RefreshLastUsedDevice(); + + if (unlikely(m_inScene)) + return D3DERR_SCENE_IN_SCENE; + + HRESULT hr = m_d3d9->BeginScene(); + + if (likely(SUCCEEDED(hr))) + m_inScene = true; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::EndScene() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::EndScene"); + + RefreshLastUsedDevice(); + + if (unlikely(!m_inScene)) + return D3DERR_SCENE_NOT_IN_SCENE; + + HRESULT hr = m_d3d9->EndScene(); + + if (likely(SUCCEEDED(hr))) { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (d3dOptions->forceProxiedPresent) { + // If we have drawn anything, we need to make sure we blit back + // the results onto the D3D5 render target before we flip it + if (m_commonIntf->HasDrawn()) + BlitToDDrawSurface(m_rt->GetProxied(), m_rt->GetD3D9()); + + m_rt->GetProxied()->Flip(static_cast(m_commonIntf->GetFlipRTSurface()), + m_commonIntf->GetFlipRTFlags()); + + if (likely(d3dOptions->backBufferGuard != D3DBackBufferGuard::Strict)) + m_commonIntf->ResetDrawTracking(); + } + + m_inScene = false; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetDirect3D(IDirect3D2 **d3d) { + Logger::debug(">>> D3D5Device::GetDirect3D"); + + if (unlikely(d3d == nullptr)) + return DDERR_INVALIDPARAMS; + + *d3d = ref(m_parent); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::SetCurrentViewport(IDirect3DViewport2 *viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::SetCurrentViewport"); + + if (unlikely(viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + Com d3d5Viewport = static_cast(viewport); + HRESULT hr = m_proxy->SetCurrentViewport(d3d5Viewport->GetProxied()); + if (unlikely(FAILED(hr))) { + Logger::debug("D3D5Device::SetCurrentViewport: Failed to set proxied viewport"); + return hr; + } + + if (unlikely(m_currentViewport == d3d5Viewport)) + return D3D_OK; + + if (likely(m_currentViewport != nullptr)) + m_currentViewport->GetCommonViewport()->SetIsCurrentViewport(false); + + m_currentViewport = d3d5Viewport.ptr(); + + m_currentViewport->GetCommonViewport()->SetIsCurrentViewport(true); + m_currentViewport->ApplyViewport(); + m_currentViewport->ApplyAndActivateLights(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetCurrentViewport(IDirect3DViewport2 **viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::GetCurrentViewport"); + + if (unlikely(viewport == nullptr)) + return D3DERR_NOCURRENTVIEWPORT; + + InitReturnPtr(viewport); + + if (unlikely(m_currentViewport == nullptr)) + return D3DERR_NOCURRENTVIEWPORT; + + *viewport = m_currentViewport.ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::SetRenderTarget(IDirectDrawSurface *surface, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::SetRenderTarget"); + + if (unlikely(surface == nullptr)) { + Logger::err("D3D5Device::SetRenderTarget: NULL render target"); + return DDERR_INVALIDPARAMS; + } + + if (unlikely(!m_commonIntf->IsWrappedSurface(surface))) { + Logger::err("D3D5Device::SetRenderTarget: Received an unwrapped RT"); + return DDERR_GENERIC; + } + + DDrawSurface* rt5 = static_cast(surface); + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->forceProxiedPresent)) { + HRESULT hrRT = m_proxy->SetRenderTarget(rt5->GetProxied(), flags); + if (unlikely(FAILED(hrRT))) { + Logger::warn("D3D5Device::SetRenderTarget: Failed to set RT"); + return hrRT; + } + } else { + // Needed to ensure proxied Z/Stencil viewport clears will work + HRESULT hrRT = m_proxy->SetRenderTarget(rt5->GetProxied(), flags); + if (unlikely(FAILED(hrRT))) + Logger::debug("D3D5Device::SetRenderTarget: Failed to set RT"); + } + + HRESULT hr = rt5->GetCommonSurface()->ValidateRTUsage(); + if (unlikely(FAILED(hr))) + return hr; + + hr = rt5->InitializeD3D9RenderTarget(); + if (unlikely(FAILED(hr))) { + Logger::err("D3D5Device::SetRenderTarget: Failed to initialize D3D9 RT"); + return hr; + } + + hr = m_d3d9->SetRenderTarget(0, rt5->GetD3D9()); + + if (likely(SUCCEEDED(hr))) { + Logger::debug("D3D5Device::SetRenderTarget: Set a new D3D9 RT"); + + m_rt = rt5; + m_ds = m_rt->GetAttachedDepthStencil(); + + HRESULT hrDS; + + if (m_ds != nullptr) { + Logger::debug("D3D5Device::SetRenderTarget: Found an attached DS"); + + hrDS = m_ds->InitializeD3D9DepthStencil(); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D5Device::SetRenderTarget: Failed to initialize/upload D3D9 DS"); + return hrDS; + } + + hrDS = m_d3d9->SetDepthStencilSurface(m_ds->GetD3D9()); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D5Device::SetRenderTarget: Failed to set D3D9 DS"); + return hrDS; + } + + Logger::debug("D3D5Device::SetRenderTarget: Set a new D3D9 DS"); + } else { + Logger::debug("D3D5Device::SetRenderTarget: RT has no depth stencil attached"); + + hrDS = m_d3d9->SetDepthStencilSurface(nullptr); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D5Device::SetRenderTarget: Failed to clear the D3D9 DS"); + return hrDS; + } + + Logger::debug("D3D5Device::SetRenderTarget: Cleared the D3D9 DS"); + } + } else { + Logger::err("D3D5Device::SetRenderTarget: Failed to set D3D9 RT"); + return hr; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetRenderTarget(IDirectDrawSurface **surface) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::GetRenderTarget"); + + if (unlikely(surface == nullptr)) + return DDERR_INVALIDPARAMS; + + *surface = m_rt.ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::Begin(D3DPRIMITIVETYPE d3dptPrimitiveType, D3DVERTEXTYPE dwVertexTypeDesc, DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::Begin"); + + m_vertexStreamInfo.d3dpt = d3dptPrimitiveType; + m_vertexStreamInfo.d3dvt = dwVertexTypeDesc; + m_vertexStreamInfo.dwFlags = dwFlags; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::BeginIndexed(D3DPRIMITIVETYPE primitive_type, D3DVERTEXTYPE fvf, void *vertices, DWORD vertex_count, DWORD flags) { + Logger::warn("!!! D3D5Device::BeginIndexed: Stub"); + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::Vertex(void *vertex) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::Vertex"); + + if (unlikely(vertex == nullptr)) + return DDERR_INVALIDPARAMS; + + if (m_vertexStreamInfo.d3dvt == D3DVT_VERTEX) { + m_vertexStream.push_back(*reinterpret_cast(vertex)); + } else if (m_vertexStreamInfo.d3dvt == D3DVT_LVERTEX) { + m_lvertexStream.push_back(*reinterpret_cast(vertex)); + } else if (m_vertexStreamInfo.d3dvt == D3DVT_TLVERTEX) { + m_tlvertexStream.push_back(*reinterpret_cast(vertex)); + } else { + Logger::warn(">>> D3D5Device::Vertex: Invalid vertex type"); + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::Index(WORD wVertexIndex) { + Logger::warn("!!! D3D5Device::Index: Stub"); + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::End(DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::End"); + + HRESULT hr; + if (m_vertexStreamInfo.d3dvt == D3DVT_VERTEX) { + hr = DrawPrimitive(m_vertexStreamInfo.d3dpt, m_vertexStreamInfo.d3dvt, m_vertexStream.data(), + m_vertexStream.size(), m_vertexStreamInfo.dwFlags); + m_vertexStream.clear(); + } else if (m_vertexStreamInfo.d3dvt == D3DVT_LVERTEX) { + hr = DrawPrimitive(m_vertexStreamInfo.d3dpt, m_vertexStreamInfo.d3dvt, m_lvertexStream.data(), + m_lvertexStream.size(), m_vertexStreamInfo.dwFlags); + m_lvertexStream.clear(); + } else if (m_vertexStreamInfo.d3dvt == D3DVT_TLVERTEX) { + hr = DrawPrimitive(m_vertexStreamInfo.d3dpt, m_vertexStreamInfo.d3dvt, m_tlvertexStream.data(), + m_tlvertexStream.size(), m_vertexStreamInfo.dwFlags); + m_tlvertexStream.clear(); + } else { + Logger::warn(">>> D3D5Device::End: Invalid vertex type"); + return DDERR_INVALIDPARAMS; + } + + if (unlikely(FAILED(hr))) + Logger::warn(">>> D3D5Device::End: Failed call to DrawPrimitive"); + + m_vertexStreamInfo = { }; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetRenderState(D3DRENDERSTATETYPE dwRenderStateType, LPDWORD lpdwRenderState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(str::format(">>> D3D5Device::GetRenderState: ", dwRenderStateType)); + + if (unlikely(lpdwRenderState == nullptr)) + return DDERR_INVALIDPARAMS; + + // As opposed to D3D7, D3D5 does not error out on + // unknown or invalid render states. + if (unlikely(!IsValidD3D5RenderStateType(dwRenderStateType))) { + Logger::debug(str::format("D3D5Device::GetRenderState: Invalid render state ", dwRenderStateType)); + *lpdwRenderState = 0; + return D3D_OK; + } + + d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(dwRenderStateType); + + switch (dwRenderStateType) { + // Most render states translate 1:1 to D3D9 + default: + break; + + // Replacement for later implemented GetTexture calls + case D3DRENDERSTATE_TEXTUREHANDLE: + *lpdwRenderState = m_textureHandle; + return D3D_OK; + + case D3DRENDERSTATE_ANTIALIAS: + *lpdwRenderState = m_antialias; + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESS: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, lpdwRenderState); + return D3D_OK; + + // Always enabled on later APIs, though default FALSE in D3D5 + case D3DRENDERSTATE_TEXTUREPERSPECTIVE: + *lpdwRenderState = TRUE; + return D3D_OK; + + // Not implemented in DXVK, but retrieve it as it were + case D3DRENDERSTATE_WRAPU: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + *lpdwRenderState = value9 & D3DWRAP_U; + return D3D_OK; + } + + // Not implemented in DXVK, but retrieve it as it were + case D3DRENDERSTATE_WRAPV: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + *lpdwRenderState = value9 & D3DWRAP_V; + return D3D_OK; + } + + case D3DRENDERSTATE_LINEPATTERN: + *lpdwRenderState = bit::cast(m_linePattern); + return D3D_OK; + + case D3DRENDERSTATE_MONOENABLE: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_ROP2: + *lpdwRenderState = R2_COPYPEN; + return D3D_OK; + + case D3DRENDERSTATE_PLANEMASK: + *lpdwRenderState = 0; + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREMAG: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MAGFILTER, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREMIN: { + DWORD minFilter = 0; + DWORD mipFilter = 0; + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MINFILTER, &minFilter); + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, &mipFilter); + *lpdwRenderState = DecodeTextureMinValues(minFilter, mipFilter); + return D3D_OK; + } + + case D3DRENDERSTATE_TEXTUREMAPBLEND: + *lpdwRenderState = m_textureMapBlend; + return D3D_OK; + + // Replaced by D3DRENDERSTATE_ALPHABLENDENABLE + case D3DRENDERSTATE_BLENDENABLE: + State9 = d3d9::D3DRS_ALPHABLENDENABLE; + break; + + case D3DRENDERSTATE_ZVISIBLE: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_SUBPIXEL: + case D3DRENDERSTATE_SUBPIXELX: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_STIPPLEDALPHA: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_STIPPLEENABLE: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_EDGEANTIALIAS: + State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE; + break; + + case D3DRENDERSTATE_COLORKEYENABLE: + *lpdwRenderState = m_colorKeyEnabled; + return D3D_OK; + + case D3DRENDERSTATE_ALPHABLENDENABLE_OLD: + State9 = d3d9::D3DRS_ALPHABLENDENABLE; + break; + + case D3DRENDERSTATE_BORDERCOLOR: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_BORDERCOLOR, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESSU: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESSV: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_ADDRESSV, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_MIPMAPLODBIAS: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MIPMAPLODBIAS, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_ZBIAS: { + DWORD bias = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_DEPTHBIAS, &bias); + *lpdwRenderState = static_cast(bit::cast(bias) * ddrawCaps::ZBIAS_SCALE_INV); + return D3D_OK; + } + + case D3DRENDERSTATE_ANISOTROPY: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MAXANISOTROPY, lpdwRenderState); + return D3D_OK; + + // Not mentioned in the D3D5 docs, but seen in the wild. D3D6 docs state: + // "Flush any pending DrawPrimitive batches. When rendering with texture handles + // (using the IDirect3DDevice2 interface) you must flush batched primitives + // after modifying the current texture surface." + case D3DRENDERSTATE_FLUSHBATCH: + *lpdwRenderState = TRUE; + return D3D_OK; + + case D3DRENDERSTATE_STIPPLEPATTERN00: + case D3DRENDERSTATE_STIPPLEPATTERN01: + case D3DRENDERSTATE_STIPPLEPATTERN02: + case D3DRENDERSTATE_STIPPLEPATTERN03: + case D3DRENDERSTATE_STIPPLEPATTERN04: + case D3DRENDERSTATE_STIPPLEPATTERN05: + case D3DRENDERSTATE_STIPPLEPATTERN06: + case D3DRENDERSTATE_STIPPLEPATTERN07: + case D3DRENDERSTATE_STIPPLEPATTERN08: + case D3DRENDERSTATE_STIPPLEPATTERN09: + case D3DRENDERSTATE_STIPPLEPATTERN10: + case D3DRENDERSTATE_STIPPLEPATTERN11: + case D3DRENDERSTATE_STIPPLEPATTERN12: + case D3DRENDERSTATE_STIPPLEPATTERN13: + case D3DRENDERSTATE_STIPPLEPATTERN14: + case D3DRENDERSTATE_STIPPLEPATTERN15: + case D3DRENDERSTATE_STIPPLEPATTERN16: + case D3DRENDERSTATE_STIPPLEPATTERN17: + case D3DRENDERSTATE_STIPPLEPATTERN18: + case D3DRENDERSTATE_STIPPLEPATTERN19: + case D3DRENDERSTATE_STIPPLEPATTERN20: + case D3DRENDERSTATE_STIPPLEPATTERN21: + case D3DRENDERSTATE_STIPPLEPATTERN22: + case D3DRENDERSTATE_STIPPLEPATTERN23: + case D3DRENDERSTATE_STIPPLEPATTERN24: + case D3DRENDERSTATE_STIPPLEPATTERN25: + case D3DRENDERSTATE_STIPPLEPATTERN26: + case D3DRENDERSTATE_STIPPLEPATTERN27: + case D3DRENDERSTATE_STIPPLEPATTERN28: + case D3DRENDERSTATE_STIPPLEPATTERN29: + case D3DRENDERSTATE_STIPPLEPATTERN30: + case D3DRENDERSTATE_STIPPLEPATTERN31: + *lpdwRenderState = 0; + return D3D_OK; + } + + // This call will never fail + return m_d3d9->GetRenderState(State9, lpdwRenderState); + } + + HRESULT STDMETHODCALLTYPE D3D5Device::SetRenderState(D3DRENDERSTATETYPE dwRenderStateType, DWORD dwRenderState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(str::format(">>> D3D5Device::SetRenderState: ", dwRenderStateType)); + + // As opposed to D3D7, D3D5 does not error out on + // unknown or invalid render states. + if (unlikely(!IsValidD3D5RenderStateType(dwRenderStateType))) { + Logger::debug(str::format("D3D5Device::SetRenderState: Invalid render state ", dwRenderStateType)); + return D3D_OK; + } + + d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(dwRenderStateType); + + switch (dwRenderStateType) { + // Most render states translate 1:1 to D3D9 + default: + break; + + // Replacement for later implemented SetTexture calls + case D3DRENDERSTATE_TEXTUREHANDLE: { + DDrawSurface* surface = nullptr; + + if (likely(dwRenderState != 0)) { + surface = m_commonIntf->GetSurfaceFromTextureHandle(dwRenderState); + if (unlikely(surface == nullptr)) + return DDERR_INVALIDPARAMS; + } + + HRESULT hr = SetTextureInternal(surface, dwRenderState); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(surface == nullptr)) + m_bridge->SetColorKeyState(false); + + break; + } + + case D3DRENDERSTATE_ANTIALIAS: { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (likely(d3dOptions->emulateFSAA == FSAAEmulation::Disabled)) { + if (unlikely(dwRenderState == D3DANTIALIAS_SORTDEPENDENT + || dwRenderState == D3DANTIALIAS_SORTINDEPENDENT)) + Logger::warn("D3D5Device::SetRenderState: Device does not expose FSAA emulation"); + return D3D_OK; + } + + State9 = d3d9::D3DRS_MULTISAMPLEANTIALIAS; + m_antialias = dwRenderState; + dwRenderState = m_antialias == D3DANTIALIAS_SORTDEPENDENT + || m_antialias == D3DANTIALIAS_SORTINDEPENDENT + || d3dOptions->emulateFSAA == FSAAEmulation::Forced ? TRUE : FALSE; + break; + } + + case D3DRENDERSTATE_TEXTUREADDRESS: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, dwRenderState); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSV, dwRenderState); + return D3D_OK; + + // Always enabled on later APIs, though default FALSE in D3D5 + case D3DRENDERSTATE_TEXTUREPERSPECTIVE: + return D3D_OK; + + // Not implemented in DXVK, but forward it anyway + case D3DRENDERSTATE_WRAPU: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + if (dwRenderState == TRUE) { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & D3DWRAP_U); + } else { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & ~D3DWRAP_U); + } + return D3D_OK; + } + + // Not implemented in DXVK, but forward it anyway + case D3DRENDERSTATE_WRAPV: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + if (dwRenderState == TRUE) { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & D3DWRAP_V); + } else { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & ~D3DWRAP_V); + } + return D3D_OK; + } + + // TODO: Implement D3DRS_LINEPATTERN - vkCmdSetLineRasterizationModeEXT + // and advertise support with D3DPRASTERCAPS_PAT once that is done + case D3DRENDERSTATE_LINEPATTERN: + static bool s_linePatternErrorShown; + + if (!std::exchange(s_linePatternErrorShown, true)) + Logger::warn("D3D5Device::SetRenderState: Unimplemented render state D3DRS_LINEPATTERN"); + + m_linePattern = bit::cast(dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_MONOENABLE: + static bool s_monoEnableErrorShown; + + if (dwRenderState && !std::exchange(s_monoEnableErrorShown, true)) + Logger::warn("D3D5Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_MONOENABLE"); + + return D3D_OK; + + case D3DRENDERSTATE_ROP2: + static bool s_ROP2ErrorShown; + + if (!std::exchange(s_ROP2ErrorShown, true)) + Logger::warn("D3D5Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_ROP2"); + + return D3D_OK; + + // "This render state is not supported by the software rasterizers, and is often ignored by hardware drivers." + case D3DRENDERSTATE_PLANEMASK: + return D3D_OK; + + // Docs: "[...] only the first two (D3DFILTER_NEAREST and + // D3DFILTER_LINEAR) are valid with D3DRENDERSTATE_TEXTUREMAG." + case D3DRENDERSTATE_TEXTUREMAG: { + switch (dwRenderState) { + case D3DFILTER_NEAREST: + case D3DFILTER_LINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MAGFILTER, dwRenderState); + break; + default: + break; + } + return D3D_OK; + } + + case D3DRENDERSTATE_TEXTUREMIN: { + switch (dwRenderState) { + case D3DFILTER_NEAREST: + case D3DFILTER_LINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, dwRenderState); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_NONE); + break; + // "The closest mipmap level is chosen and a point filter is applied." + case D3DFILTER_MIPNEAREST: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_POINT); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_POINT); + break; + // "The closest mipmap level is chosen and a bilinear filter is applied within it." + case D3DFILTER_MIPLINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_LINEAR); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_POINT); + break; + // "The two closest mipmap levels are chosen and then a linear + // blend is used between point filtered samples of each level." + case D3DFILTER_LINEARMIPNEAREST: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_POINT); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_LINEAR); + break; + // "The two closest mipmap levels are chosen and then combined using a bilinear filter." + case D3DFILTER_LINEARMIPLINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_LINEAR); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_LINEAR); + break; + default: + break; + } + return D3D_OK; + } + + case D3DRENDERSTATE_TEXTUREMAPBLEND: + m_textureMapBlend = dwRenderState; + + switch (dwRenderState) { + // "In this mode, the RGB and alpha values of the texture replace + // the colors that would have been used with no texturing." + case D3DTBLEND_DECAL: + case D3DTBLEND_COPY: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_CURRENT); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_CURRENT); + break; + // "In this mode, the RGB values of the texture are multiplied with the RGB values + // that would have been used with no texturing. Any alpha values in the texture + // replace the alpha values in the colors that would have been used with no texturing; + // if the texture does not contain an alpha component, alpha values at the vertices + // in the source are interpolated between vertices." + case D3DTBLEND_MODULATE: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "In this mode, the RGB and alpha values of the texture are blended with the colors + // that would have been used with no texturing, according to the following formulas [...]" + case D3DTBLEND_DECALALPHA: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_BLENDTEXTUREALPHA); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "In this mode, the RGB values of the texture are multiplied with the RGB values that + // would have been used with no texturing, and the alpha values of the texture + // are multiplied with the alpha values that would have been used with no texturing." + case D3DTBLEND_MODULATEALPHA: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "Add the Gouraud interpolants to the texture lookup with saturation semantics + // (that is, if the color value overflows it is set to the maximum possible value)." + case D3DTBLEND_ADD: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_ADD); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // Unsupported + default: + case D3DTBLEND_DECALMASK: + case D3DTBLEND_MODULATEMASK: + break; + } + + return D3D_OK; + + // Replaced by D3DRENDERSTATE_ALPHABLENDENABLE + case D3DRENDERSTATE_BLENDENABLE: + State9 = d3d9::D3DRS_ALPHABLENDENABLE; + break; + + // Safe to ignore. Docs state: "Direct3D's retained mode uses this operation as a + // quick-reject test: it does the z-visible test on the bounding box of a set of + // primitives and only renders them if it returns TRUE." + case D3DRENDERSTATE_ZVISIBLE: + return D3D_OK; + + // Docs state: "Most hardware either doesn't support it (always off) or + // always supports it (always on).", and "All hardware should be subpixel correct. + // Some software rasterizers are not subpixel correct because of the performance loss." + case D3DRENDERSTATE_SUBPIXEL: + case D3DRENDERSTATE_SUBPIXELX: + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEDALPHA: + static bool s_stippledAlphaErrorShown; + + if (dwRenderState && !std::exchange(s_stippledAlphaErrorShown, true)) + Logger::warn("D3D5Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_STIPPLEDALPHA"); + + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEENABLE: + static bool s_stippleEnableErrorShown; + + if (dwRenderState && !std::exchange(s_stippleEnableErrorShown, true)) + Logger::warn("D3D5Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_STIPPLEENABLE"); + + return D3D_OK; + + case D3DRENDERSTATE_EDGEANTIALIAS: + State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE; + break; + + case D3DRENDERSTATE_COLORKEYENABLE: { + m_colorKeyEnabled = dwRenderState; + + DDrawSurface* surface = m_textureHandle != 0 ? m_commonIntf->GetSurfaceFromTextureHandle(m_textureHandle) : nullptr; + const bool validColorKey = surface != nullptr ? surface->GetCommonSurface()->HasValidColorKey() : false; + m_bridge->SetColorKeyState(m_colorKeyEnabled && validColorKey); + if (m_colorKeyEnabled && validColorKey) { + DDCOLORKEY normalizedColorKey = surface->GetCommonSurface()->GetColorKeyNormalized(); + m_bridge->SetColorKey(normalizedColorKey.dwColorSpaceLowValue, + normalizedColorKey.dwColorSpaceHighValue); + } + + return D3D_OK; + } + + case D3DRENDERSTATE_ALPHABLENDENABLE_OLD: + State9 = d3d9::D3DRS_ALPHABLENDENABLE; + break; + + case D3DRENDERSTATE_BORDERCOLOR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_BORDERCOLOR, dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESSU: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESSV: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSV, dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_MIPMAPLODBIAS: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPMAPLODBIAS, dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_ZBIAS: + State9 = d3d9::D3DRS_DEPTHBIAS; + dwRenderState = bit::cast(static_cast(dwRenderState) * ddrawCaps::ZBIAS_SCALE); + break; + + case D3DRENDERSTATE_ANISOTROPY: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MAXANISOTROPY, dwRenderState); + return D3D_OK; + + // Not mentioned in the D3D5 docs, but seen in the wild. D3D6 docs state: + // "Flush any pending DrawPrimitive batches. When rendering with texture handles + // (using the IDirect3DDevice2 interface) you must flush batched primitives + // after modifying the current texture surface." + case D3DRENDERSTATE_FLUSHBATCH: + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEPATTERN00: + case D3DRENDERSTATE_STIPPLEPATTERN01: + case D3DRENDERSTATE_STIPPLEPATTERN02: + case D3DRENDERSTATE_STIPPLEPATTERN03: + case D3DRENDERSTATE_STIPPLEPATTERN04: + case D3DRENDERSTATE_STIPPLEPATTERN05: + case D3DRENDERSTATE_STIPPLEPATTERN06: + case D3DRENDERSTATE_STIPPLEPATTERN07: + case D3DRENDERSTATE_STIPPLEPATTERN08: + case D3DRENDERSTATE_STIPPLEPATTERN09: + case D3DRENDERSTATE_STIPPLEPATTERN10: + case D3DRENDERSTATE_STIPPLEPATTERN11: + case D3DRENDERSTATE_STIPPLEPATTERN12: + case D3DRENDERSTATE_STIPPLEPATTERN13: + case D3DRENDERSTATE_STIPPLEPATTERN14: + case D3DRENDERSTATE_STIPPLEPATTERN15: + case D3DRENDERSTATE_STIPPLEPATTERN16: + case D3DRENDERSTATE_STIPPLEPATTERN17: + case D3DRENDERSTATE_STIPPLEPATTERN18: + case D3DRENDERSTATE_STIPPLEPATTERN19: + case D3DRENDERSTATE_STIPPLEPATTERN20: + case D3DRENDERSTATE_STIPPLEPATTERN21: + case D3DRENDERSTATE_STIPPLEPATTERN22: + case D3DRENDERSTATE_STIPPLEPATTERN23: + case D3DRENDERSTATE_STIPPLEPATTERN24: + case D3DRENDERSTATE_STIPPLEPATTERN25: + case D3DRENDERSTATE_STIPPLEPATTERN26: + case D3DRENDERSTATE_STIPPLEPATTERN27: + case D3DRENDERSTATE_STIPPLEPATTERN28: + case D3DRENDERSTATE_STIPPLEPATTERN29: + case D3DRENDERSTATE_STIPPLEPATTERN30: + case D3DRENDERSTATE_STIPPLEPATTERN31: + static bool s_stipplePatternErrorShown; + + if (!std::exchange(s_stipplePatternErrorShown, true)) + Logger::warn("D3D5Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_STIPPLEPATTERN"); + + return D3D_OK; + } + + // This call will never fail + return m_d3d9->SetRenderState(State9, dwRenderState); + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetLightState(D3DLIGHTSTATETYPE dwLightStateType, LPDWORD lpdwLightState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::GetLightState"); + + switch (dwLightStateType) { + case D3DLIGHTSTATE_MATERIAL: + *lpdwLightState = m_materialHandle; + break; + case D3DLIGHTSTATE_AMBIENT: + m_d3d9->GetRenderState(d3d9::D3DRS_AMBIENT, lpdwLightState); + break; + case D3DLIGHTSTATE_COLORMODEL: + *lpdwLightState = D3DCOLOR_RGB; + break; + case D3DLIGHTSTATE_FOGMODE: + m_d3d9->GetRenderState(d3d9::D3DRS_FOGVERTEXMODE, lpdwLightState); + break; + case D3DLIGHTSTATE_FOGSTART: + m_d3d9->GetRenderState(d3d9::D3DRS_FOGSTART, lpdwLightState); + break; + case D3DLIGHTSTATE_FOGEND: + m_d3d9->GetRenderState(d3d9::D3DRS_FOGEND, lpdwLightState); + break; + case D3DLIGHTSTATE_FOGDENSITY: + m_d3d9->GetRenderState(d3d9::D3DRS_FOGDENSITY, lpdwLightState); + break; + default: + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::SetLightState(D3DLIGHTSTATETYPE dwLightStateType, DWORD dwLightState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::SetLightState"); + + switch (dwLightStateType) { + case D3DLIGHTSTATE_MATERIAL: { + if (unlikely(!dwLightState)) { + m_materialHandle = dwLightState; + return D3D_OK; + } + + d3d9::D3DMATERIAL9* material9 = m_parent->GetCommonD3DInterface()->GetD3D9MaterialFromHandle(dwLightState); + if (unlikely(material9 == nullptr)) + return DDERR_INVALIDPARAMS; + + m_materialHandle = dwLightState; + Logger::debug(str::format("D3D5Device::SetLightState: Applying material nr. ", dwLightState, " to D3D9")); + m_d3d9->SetMaterial(material9); + + break; + } + case D3DLIGHTSTATE_AMBIENT: + m_d3d9->SetRenderState(d3d9::D3DRS_AMBIENT, dwLightState); + break; + case D3DLIGHTSTATE_COLORMODEL: + if (unlikely(dwLightState != D3DCOLOR_RGB)) + Logger::warn("D3D5Device::SetLightState: Unsupported D3DLIGHTSTATE_COLORMODEL"); + break; + case D3DLIGHTSTATE_FOGMODE: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGVERTEXMODE, dwLightState); + break; + case D3DLIGHTSTATE_FOGSTART: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGSTART, dwLightState); + break; + case D3DLIGHTSTATE_FOGEND: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGEND, dwLightState); + break; + case D3DLIGHTSTATE_FOGDENSITY: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGDENSITY, dwLightState); + break; + default: + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::SetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D5Device::SetTransform"); + + // Need to also proxy for viewport TransformVertices calls to work + HRESULT hr = m_proxy->SetTransform(state, matrix); + if (unlikely(FAILED(hr))) + return hr; + + return m_d3d9->SetTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D5Device::GetTransform"); + return m_d3d9->GetTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D5Device::MultiplyTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D5Device::MultiplyTransform"); + + // Need to also proxy for viewport TransformVertices calls to work + HRESULT hr = m_proxy->MultiplyTransform(state, matrix); + if (unlikely(FAILED(hr))) + return hr; + + return m_d3d9->MultiplyTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D5Device::DrawPrimitive(D3DPRIMITIVETYPE primitive_type, D3DVERTEXTYPE vertex_type, void *vertices, DWORD vertex_count, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::DrawPrimitive"); + + RefreshLastUsedDevice(); + + if (unlikely(!vertex_count)) + return D3D_OK; + + if (unlikely(vertices == nullptr)) + return DDERR_INVALIDPARAMS; + + DWORD vertex_type5 = ConvertVertexType(vertex_type); + + HandlePreDrawFlags(flags, vertex_type5); + HandlePreDrawLegacyProjection(flags); + + m_d3d9->SetFVF(vertex_type5); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(primitive_type), + GetPrimitiveCount(primitive_type, vertex_count), + vertices, + GetFVFSize(vertex_type5)); + + HandlePostDrawLegacyProjection(); + HandlePostDrawFlags(flags, vertex_type5); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D5Device::DrawPrimitive: Failed D3D9 call to DrawPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::DrawIndexedPrimitive(D3DPRIMITIVETYPE primitive_type, D3DVERTEXTYPE fvf, void *vertices, DWORD vertex_count, WORD *indices, DWORD index_count, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::DrawIndexedPrimitive"); + + RefreshLastUsedDevice(); + + if (unlikely(!vertex_count || !index_count)) + return D3D_OK; + + if (unlikely(vertices == nullptr || indices == nullptr)) + return DDERR_INVALIDPARAMS; + + const DWORD fvf5 = ConvertVertexType(fvf); + + HandlePreDrawFlags(flags, fvf5); + HandlePreDrawLegacyProjection(flags); + + m_d3d9->SetFVF(fvf5); + HRESULT hr = m_d3d9->DrawIndexedPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(primitive_type), + 0, + vertex_count, + GetPrimitiveCount(primitive_type, index_count), + indices, + d3d9::D3DFMT_INDEX16, + vertices, + GetFVFSize(fvf5)); + + HandlePostDrawLegacyProjection(); + HandlePostDrawFlags(flags, fvf5); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D5Device::DrawIndexedPrimitive: Failed D3D9 call to DrawIndexedPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::SetClipStatus(D3DCLIPSTATUS *clip_status) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::SetClipStatus"); + + if (unlikely(clip_status == nullptr)) + return DDERR_INVALIDPARAMS; + + m_clipStatus = *clip_status; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Device::GetClipStatus(D3DCLIPSTATUS *clip_status) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D5Device::GetClipStatus"); + + if (unlikely(clip_status == nullptr)) + return DDERR_INVALIDPARAMS; + + *clip_status = m_clipStatus; + + return D3D_OK; + } + + void D3D5Device::InitializeDS() { + if (!m_rt->IsInitialized()) + m_rt->InitializeD3D9RenderTarget(); + + m_ds = m_rt->GetAttachedDepthStencil(); + + if (m_ds != nullptr) { + Logger::debug("D3D5Device::InitializeDS: Found an attached DS"); + + HRESULT hrDS = m_ds->InitializeD3D9DepthStencil(); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D5Device::InitializeDS: Failed to initialize D3D9 DS"); + } else { + Logger::info("D3D5Device::InitializeDS: Got depth stencil from RT"); + + DDSURFACEDESC descDS; + descDS.dwSize = sizeof(DDSURFACEDESC); + m_ds->GetProxied()->GetSurfaceDesc(&descDS); + Logger::debug(str::format("D3D5Device::InitializeDS: DepthStencil: ", descDS.dwWidth, "x", descDS.dwHeight)); + + HRESULT hrDS9 = m_d3d9->SetDepthStencilSurface(m_ds->GetD3D9()); + if(unlikely(FAILED(hrDS9))) { + Logger::err("D3D5Device::InitializeDS: Failed to set D3D9 depth stencil"); + } else { + // This needs to act like an auto depth stencil of sorts, so manually enable z-buffering + m_d3d9->SetRenderState(d3d9::D3DRS_ZENABLE, d3d9::D3DZB_TRUE); + } + } + } else { + Logger::info("D3D5Device::InitializeDS: RT has no depth stencil attached"); + m_d3d9->SetDepthStencilSurface(nullptr); + // Should be superfluous, but play it safe + m_d3d9->SetRenderState(d3d9::D3DRS_ZENABLE, d3d9::D3DZB_FALSE); + } + } + + inline void D3D5Device::AddViewportInternal(IDirect3DViewport2* viewport) { + D3D5Viewport* d3d5Viewport = static_cast(viewport); + + auto it = std::find(m_viewports.begin(), m_viewports.end(), d3d5Viewport); + if (unlikely(it != m_viewports.end())) { + Logger::warn("D3D5Device::AddViewportInternal: Pre-existing viewport found"); + } else { + m_viewports.push_back(d3d5Viewport); + d3d5Viewport->GetCommonViewport()->SetD3D5Device(this); + } + } + + inline void D3D5Device::DeleteViewportInternal(IDirect3DViewport2* viewport) { + D3D5Viewport* d3d5Viewport = static_cast(viewport); + + auto it = std::find(m_viewports.begin(), m_viewports.end(), d3d5Viewport); + if (likely(it != m_viewports.end())) { + m_viewports.erase(it); + d3d5Viewport->GetCommonViewport()->SetD3D5Device(nullptr); + } else { + Logger::warn("D3D5Device::DeleteViewportInternal: Viewport not found"); + } + } + + inline HRESULT D3D5Device::SetTextureInternal(DDrawSurface* surface, DWORD textureHandle) { + Logger::debug(">>> D3D5Device::SetTextureInternal"); + + HRESULT hr; + + // Unbinding texture stages + if (surface == nullptr) { + Logger::debug("D3D5Device::SetTextureInternal: Unbiding D3D9 texture"); + + hr = m_d3d9->SetTexture(0, nullptr); + + if (likely(SUCCEEDED(hr))) { + if (m_textureHandle != 0) { + Logger::debug("D3D5Device::SetTextureInternal: Unbinding local texture"); + m_textureHandle = 0; + } + } else { + Logger::err("D3D5Device::SetTextureInternal: Failed to unbind D3D9 texture"); + } + + return hr; + } + + Logger::debug("D3D5Device::SetTextureInternal: Binding D3D9 texture"); + + // Only upload textures if any sort of blit/lock operation + // has been performed on them since the last SetTexture call, + // or textures which have been used on a different device, and + // need their D3D9 object to be reinitialized at this point + if (surface->GetCommonSurface()->HasDirtyMipMaps() || + unlikely(surface->GetD3D9Device() != m_d3d9.ptr())) { + hr = surface->InitializeOrUploadD3D9(); + if (unlikely(FAILED(hr))) { + Logger::err("D3D5Device::SetTextureInternal: Failed to initialize/upload D3D9 texture"); + return hr; + } + + surface->GetCommonSurface()->UnDirtyMipMaps(); + } else { + Logger::debug("D3D5Device::SetTextureInternal: Skipping upload of texture and mip maps"); + } + + // Only fast skip on D3D9 side, since we want to ensure + // color keying is applied properly even in the case + // of the same texture being set again (color key may change) + //if (unlikely(m_textureHandle == textureHandle)) + //return D3D_OK; + + d3d9::IDirect3DTexture9* tex9 = surface->GetD3D9Texture(); + + if (likely(tex9 != nullptr)) { + hr = m_d3d9->SetTexture(0, tex9); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D5Device::SetTextureInternal: Failed to bind D3D9 texture"); + return hr; + } + + // "Any alpha values in the texture replace the alpha values in the colors that would + // have been used with no texturing; if the texture does not contain an alpha component, + // alpha values at the vertices in the source are interpolated between vertices." + if (m_textureMapBlend == D3DTBLEND_MODULATE) { + const DWORD textureOp = surface->GetCommonSurface()->IsAlphaFormat() ? D3DTOP_SELECTARG1 : D3DTOP_MODULATE; + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, textureOp); + } + + const bool validColorKey = surface->GetCommonSurface()->HasValidColorKey(); + m_bridge->SetColorKeyState(m_colorKeyEnabled && validColorKey); + if (m_colorKeyEnabled && validColorKey) { + DDCOLORKEY normalizedColorKey = surface->GetCommonSurface()->GetColorKeyNormalized(); + m_bridge->SetColorKey(normalizedColorKey.dwColorSpaceLowValue, + normalizedColorKey.dwColorSpaceHighValue); + } + } + + m_textureHandle = textureHandle; + + return D3D_OK; + } + +} diff --git a/src/ddraw/d3d5/d3d5_device.h b/src/ddraw/d3d5/d3d5_device.h new file mode 100644 index 00000000000..ab571706830 --- /dev/null +++ b/src/ddraw/d3d5/d3d5_device.h @@ -0,0 +1,252 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_options.h" +#include "../ddraw_caps.h" + +#include "../d3d_multithread.h" +#include "../ddraw_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +#include "d3d5_interface.h" +#include "d3d5_viewport.h" + +#include + +namespace dxvk { + + class DDrawCommonInterface; + class DDrawSurface; + class DDrawInterface; + + /** + * \brief D3D5 device implementation + */ + class D3D5Device final : public DDrawWrappedObject { + + public: + D3D5Device( + Com&& d3d5DeviceProxy, + D3D5Interface* pParent, + D3DDEVICEDESC2 Desc, + GUID deviceGUID, + d3d9::D3DPRESENT_PARAMETERS Params9, + Com&& pDevice9, + DDrawSurface* pRT, + DWORD CreationFlags9); + + ~D3D5Device(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE GetCaps(D3DDEVICEDESC *hal_desc, D3DDEVICEDESC *hel_desc); + + HRESULT STDMETHODCALLTYPE SwapTextureHandles(IDirect3DTexture2 *tex1, IDirect3DTexture2 *tex2); + + HRESULT STDMETHODCALLTYPE GetStats(D3DSTATS *stats); + + HRESULT STDMETHODCALLTYPE AddViewport(IDirect3DViewport2 *viewport); + + HRESULT STDMETHODCALLTYPE DeleteViewport(IDirect3DViewport2 *viewport); + + HRESULT STDMETHODCALLTYPE NextViewport(IDirect3DViewport2 *lpDirect3DViewport, IDirect3DViewport2 **lplpAnotherViewport, DWORD flags); + + HRESULT STDMETHODCALLTYPE EnumTextureFormats(LPD3DENUMTEXTUREFORMATSCALLBACK cb, void *ctx); + + HRESULT STDMETHODCALLTYPE BeginScene(); + + HRESULT STDMETHODCALLTYPE EndScene(); + + HRESULT STDMETHODCALLTYPE GetDirect3D(IDirect3D2 **d3d); + + HRESULT STDMETHODCALLTYPE SetCurrentViewport(IDirect3DViewport2 *viewport); + + HRESULT STDMETHODCALLTYPE GetCurrentViewport(IDirect3DViewport2 **viewport); + + HRESULT STDMETHODCALLTYPE SetRenderTarget(IDirectDrawSurface *surface, DWORD flags); + + HRESULT STDMETHODCALLTYPE GetRenderTarget(IDirectDrawSurface **surface); + + HRESULT STDMETHODCALLTYPE Begin(D3DPRIMITIVETYPE d3dptPrimitiveType, D3DVERTEXTYPE dwVertexTypeDesc, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE BeginIndexed(D3DPRIMITIVETYPE primitive_type, D3DVERTEXTYPE fvf, void *vertices, DWORD vertex_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE Vertex(void *vertex); + + HRESULT STDMETHODCALLTYPE Index(WORD wVertexIndex); + + HRESULT STDMETHODCALLTYPE End(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetRenderState(D3DRENDERSTATETYPE dwRenderStateType, LPDWORD lpdwRenderState); + + HRESULT STDMETHODCALLTYPE SetRenderState(D3DRENDERSTATETYPE dwRenderStateType, DWORD dwRenderState); + + HRESULT STDMETHODCALLTYPE GetLightState(D3DLIGHTSTATETYPE dwLightStateType, LPDWORD lpdwLightState); + + HRESULT STDMETHODCALLTYPE SetLightState(D3DLIGHTSTATETYPE dwLightStateType, DWORD dwLightState); + + HRESULT STDMETHODCALLTYPE SetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE GetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE MultiplyTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE DrawPrimitive(D3DPRIMITIVETYPE primitive_type, D3DVERTEXTYPE vertex_type, void *vertices, DWORD vertex_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitive(D3DPRIMITIVETYPE primitive_type, D3DVERTEXTYPE fvf, void *vertices, DWORD vertex_count, WORD *indices, DWORD index_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE SetClipStatus(D3DCLIPSTATUS *clip_status); + + HRESULT STDMETHODCALLTYPE GetClipStatus(D3DCLIPSTATUS *clip_status); + + void InitializeDS(); + + D3DDeviceLock LockDevice() { + return m_multithread.AcquireLock(); + } + + void EnableLegacyLights(bool isD3DLight2) { + m_bridge->SetLegacyLightsState(true, isD3DLight2); + } + + uint32_t GetTotalTextureMemory() const { + return m_totalMemory; + } + + d3d9::D3DPRESENT_PARAMETERS GetPresentParameters() const { + return m_params9; + } + + d3d9::D3DMULTISAMPLE_TYPE GetMultiSampleType() const { + return m_params9.MultiSampleType; + } + + DDrawSurface* GetRenderTarget() const { + return m_rt.ptr(); + } + + DDrawSurface* GetDepthStencil() const { + return m_ds.ptr(); + } + + D3D5Viewport* GetCurrentViewportInternal() const { + return m_currentViewport.ptr(); + } + + D3DMATERIALHANDLE GetCurrentMaterialHandle() const { + return m_materialHandle; + } + + void SetCurrentMaterialHandle(D3DMATERIALHANDLE handle) { + m_materialHandle = handle; + } + + private: + + inline void AddViewportInternal(IDirect3DViewport2* viewport); + + inline void DeleteViewportInternal(IDirect3DViewport2* viewport); + + inline HRESULT SetTextureInternal(DDrawSurface* surface, DWORD textureHandle); + + inline void RefreshLastUsedDevice() { + if (unlikely(m_commonIntf->GetD3D5Device() != this)) + m_commonIntf->SetD3D5Device(this); + } + + inline void HandlePreDrawFlags(DWORD drawFlags, DWORD vertexTypeDesc) { + // Docs: "Direct3D normally performs lighting calculations + // on any vertices that contain a vertex normal." + if (m_materialHandle == 0 || + (drawFlags & D3DDP_DONOTLIGHT) || + !(vertexTypeDesc & D3DFVF_NORMAL)) { + m_d3d9->GetRenderState(d3d9::D3DRS_LIGHTING, &m_lighting); + if (m_lighting) { + //Logger::debug("D3D5Device: Disabling lighting"); + m_d3d9->SetRenderState(d3d9::D3DRS_LIGHTING, FALSE); + } + } + } + + inline void HandlePostDrawFlags(DWORD drawFlags, DWORD vertexTypeDesc) { + if ((m_materialHandle == 0 || + (drawFlags & D3DDP_DONOTLIGHT) || + !(vertexTypeDesc & D3DFVF_NORMAL)) && m_lighting) { + //Logger::debug("D3D5Device: Enabling lighting"); + m_d3d9->SetRenderState(d3d9::D3DRS_LIGHTING, TRUE); + } + } + + inline void HandlePreDrawLegacyProjection(DWORD drawFlags) { + if (likely(m_currentViewport != nullptr)) { + m_legacyProjection = m_currentViewport->GetCommonViewport()->GetLegacyProjectionMatrix(drawFlags); + + if (m_legacyProjection != nullptr) { + //Logger::debug("D3D5Device: Applying legacy projection"); + m_d3d9->GetTransform(d3d9::D3DTS_PROJECTION, &m_projectionMatrix); + m_d3d9->MultiplyTransform(d3d9::D3DTS_PROJECTION, m_legacyProjection); + } + } + } + + inline void HandlePostDrawLegacyProjection() { + if (m_legacyProjection != nullptr) { + //Logger::debug("D3D5Device: Reverting legacy projection"); + m_d3d9->SetTransform(d3d9::D3DTS_PROJECTION, &m_projectionMatrix); + } + } + + bool m_inScene = false; + + static uint32_t s_deviceCount; + uint32_t m_deviceCount = 0; + + uint32_t m_totalMemory = 0; + + DWORD m_lighting = FALSE; + + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_bridge; + + DWORD m_creationFlags9 = 0; + D3DMultithread m_multithread; + + d3d9::D3DPRESENT_PARAMETERS m_params9; + + D3DMATERIALHANDLE m_materialHandle = 0; + D3DTEXTUREHANDLE m_textureHandle = 0; + + D3DDEVICEDESC2 m_desc; + GUID m_deviceGUID; + + Com m_rt; + Com m_ds; + + Com m_currentViewport; + std::vector> m_viewports; + + VertexStreamInfo m_vertexStreamInfo; + std::vector m_vertexStream; + std::vector m_lvertexStream; + std::vector m_tlvertexStream; + + // Value of D3DRENDERSTATE_COLORKEYENABLE + DWORD m_colorKeyEnabled = 0; + // Value of D3DRENDERSTATE_ANTIALIAS + DWORD m_antialias = D3DANTIALIAS_NONE; + // Value of D3DRENDERSTATE_LINEPATTERN + D3DLINEPATTERN m_linePattern = { }; + // Value of D3DCLIPSTATUS + D3DCLIPSTATUS m_clipStatus = { }; + // Value of D3DRENDERSTATE_TEXTUREMAPBLEND + DWORD m_textureMapBlend = D3DTBLEND_MODULATE; + + D3DMATRIX m_projectionMatrix = { }; + const D3DMATRIX* m_legacyProjection = nullptr; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d5/d3d5_interface.cpp b/src/ddraw/d3d5/d3d5_interface.cpp new file mode 100644 index 00000000000..ca477f82d89 --- /dev/null +++ b/src/ddraw/d3d5/d3d5_interface.cpp @@ -0,0 +1,559 @@ +#include "d3d5_interface.h" + +#include "d3d5_device.h" +#include "d3d5_material.h" +#include "d3d5_viewport.h" + +#include "../d3d_light.h" + +#include "../d3d3/d3d3_interface.h" + +#include "../ddraw/ddraw_interface.h" +#include "../ddraw/ddraw_surface.h" +#include "../ddraw2/ddraw2_interface.h" +#include "../ddraw2/ddraw3_surface.h" + +namespace dxvk { + + uint32_t D3D5Interface::s_intfCount = 0; + + D3D5Interface::D3D5Interface( + DDrawCommonInterface* m_commonIntf, + D3DCommonInterface* commonD3DIntf, + Com&& d3d5IntfProxy, + IUnknown* pParent) + : DDrawWrappedObject(pParent, std::move(d3d5IntfProxy), std::move(d3d9::Direct3DCreate9(D3D_SDK_VERSION))) + , m_commonIntf ( m_commonIntf ) + , m_commonD3DIntf ( commonD3DIntf ) { + // Get the bridge interface to D3D9. + if (unlikely(FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8InterfaceBridge), reinterpret_cast(&m_bridge))))) { + throw DxvkError("D3D5Interface: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + if (m_commonD3DIntf == nullptr) + m_commonD3DIntf = new D3DCommonInterface(); + + m_commonD3DIntf->SetD3D5Interface(this); + + m_bridge->EnableD3D5CompatibilityMode(); + + m_intfCount = ++s_intfCount; + + Logger::debug(str::format("D3D5Interface: Created a new interface nr. ((2-", m_intfCount, "))")); + } + + D3D5Interface::~D3D5Interface() { + if (m_commonD3DIntf->GetD3D5Interface() == this) + m_commonD3DIntf->SetD3D5Interface(nullptr); + + Logger::debug(str::format("D3D5Interface: Interface nr. ((2-", m_intfCount, ")) bites the dust")); + } + + // Interlocked refcount with the parent IDirectDraw + ULONG STDMETHODCALLTYPE D3D5Interface::AddRef() { + if (likely(m_parent != nullptr)) { + IUnknown* origin = m_commonIntf->GetOrigin(); + if (likely(origin != nullptr)) + return origin->AddRef(); + else + return m_parent->AddRef(); + } else { + return ComObjectClamp::AddRef(); + } + } + + // Interlocked refcount with the parent IDirectDraw + ULONG STDMETHODCALLTYPE D3D5Interface::Release() { + if (likely(m_parent != nullptr)) { + IUnknown* origin = m_commonIntf->GetOrigin(); + if (likely(origin != nullptr)) + return origin->Release(); + else + return m_parent->Release(); + } else { + return ComObjectClamp::Release(); + } + } + + HRESULT STDMETHODCALLTYPE D3D5Interface::QueryInterface(REFIID riid, void** ppvObject) { + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (riid == __uuidof(IDirectDraw)) { + Logger::debug("D3D5Interface::QueryInterface: Query for IDirectDraw"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (riid == __uuidof(IDirectDraw2)) { + Logger::debug("D3D5Interface::QueryInterface: Query for IDirectDraw2"); + return m_parent->QueryInterface(riid, ppvObject); + } + // Some games query for legacy d3d interfaces + if (unlikely(riid == __uuidof(IDirect3D))) { + if (m_commonD3DIntf->GetD3D3Interface() != nullptr) { + Logger::debug("D3D5Interface::QueryInterface: Query for existing IDirect3D"); + return m_commonD3DIntf->GetD3D3Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("D3D5Interface::QueryInterface: Query for IDirect3D"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + Com d3d3Intf = new D3D3Interface(m_commonIntf, m_commonD3DIntf.ptr(), std::move(ppvProxyObject), m_parent); + m_commonIntf->SetD3D3Interface(d3d3Intf.ptr()); + *ppvObject = d3d3Intf.ref(); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE D3D5Interface::EnumDevices(LPD3DENUMDEVICESCALLBACK lpEnumDevicesCallback, LPVOID lpUserArg) { + Logger::debug(">>> D3D5Interface::EnumDevices"); + + if (unlikely(lpEnumDevicesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // D3D5 reports both HAL and HEL caps for any type of device, + // with minor differences between the two. Note that the + // device listing order matters, so list RAMP first, RGB second, + // and HAL last. A RAMP device also needs to be advertised in D3D5, + // since some games like Resident Evil expect it to be present. + + // RAMP device (monochrome), this is expected to be exposed + GUID guidRAMP = IID_IDirect3DRampDevice; + // The caps of a RAMP device are mostly identical to an RGB device + D3DDEVICEDESC2 desc2RAMP_HAL = GetD3D5Caps(IID_IDirect3DRGBDevice, d3dOptions); + D3DDEVICEDESC2 desc2RAMP_HEL = desc2RAMP_HAL; + D3DDEVICEDESC descRAMP_HAL = { }; + D3DDEVICEDESC descRAMP_HEL = { }; + desc2RAMP_HAL.dwFlags = 0; + desc2RAMP_HAL.dcmColorModel = 0; + // RAMP devices use a monochrome color model + desc2RAMP_HEL.dcmColorModel = D3DCOLOR_MONO; + // Some applications apparently care about RGB texture caps + desc2RAMP_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc2RAMP_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc2RAMP_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + desc2RAMP_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + memcpy(&descRAMP_HAL, &desc2RAMP_HAL, sizeof(D3DDEVICEDESC2)); + memcpy(&descRAMP_HEL, &desc2RAMP_HEL, sizeof(D3DDEVICEDESC2)); + static char deviceDescRAMP[100] = "D5VK RAMP"; + static char deviceNameRAMP[100] = "D5VK RAMP"; + + HRESULT hr = lpEnumDevicesCallback(&guidRAMP, &deviceDescRAMP[0], &deviceNameRAMP[0], + &descRAMP_HAL, &descRAMP_HEL, lpUserArg); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + // Software emulation, this is expected to be exposed + GUID guidRGB = IID_IDirect3DRGBDevice; + D3DDEVICEDESC2 desc2RGB_HAL = GetD3D5Caps(IID_IDirect3DRGBDevice, d3dOptions); + D3DDEVICEDESC2 desc2RGB_HEL = desc2RGB_HAL; + D3DDEVICEDESC descRGB_HAL = { }; + D3DDEVICEDESC descRGB_HEL = { }; + desc2RGB_HAL.dwFlags = 0; + desc2RGB_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + desc2RGB_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc2RGB_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc2RGB_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + desc2RGB_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + memcpy(&descRGB_HAL, &desc2RGB_HAL, sizeof(D3DDEVICEDESC2)); + memcpy(&descRGB_HEL, &desc2RGB_HEL, sizeof(D3DDEVICEDESC2)); + static char deviceDescRGB[100] = "D5VK RGB"; + static char deviceNameRGB[100] = "D5VK RGB"; + + hr = lpEnumDevicesCallback(&guidRGB, &deviceDescRGB[0], &deviceNameRGB[0], + &descRGB_HAL, &descRGB_HEL, lpUserArg); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + // Hardware acceleration + GUID guidHAL = IID_IDirect3DHALDevice; + D3DDEVICEDESC2 desc2HAL_HAL = GetD3D5Caps(IID_IDirect3DHALDevice, d3dOptions); + D3DDEVICEDESC2 desc2HAL_HEL = desc2HAL_HAL; + D3DDEVICEDESC descHAL_HAL = { }; + D3DDEVICEDESC descHAL_HEL = { }; + desc2HAL_HEL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + desc2HAL_HEL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc2HAL_HEL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + desc2HAL_HEL.dwDevCaps &= ~D3DDEVCAPS_HWTRANSFORMANDLIGHT + & ~D3DDEVCAPS_DRAWPRIMITIVES2 + & ~D3DDEVCAPS_DRAWPRIMITIVES2EX; + memcpy(&descHAL_HAL, &desc2HAL_HAL, sizeof(D3DDEVICEDESC2)); + memcpy(&descHAL_HEL, &desc2HAL_HEL, sizeof(D3DDEVICEDESC2)); + static char deviceDescHAL[100] = "D5VK HAL"; + static char deviceNameHAL[100] = "D5VK HAL"; + + hr = lpEnumDevicesCallback(&guidHAL, &deviceDescHAL[0], &deviceNameHAL[0], + &descHAL_HAL, &descHAL_HEL, lpUserArg); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Interface::CreateLight(LPDIRECT3DLIGHT *lplpDirect3DLight, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D5Interface::CreateLight"); + + if (unlikely(lplpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DLight); + + *lplpDirect3DLight = ref(new D3DLight(nullptr, this)); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Interface::CreateMaterial(LPDIRECT3DMATERIAL2 *lplpDirect3DMaterial, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D5Interface::CreateMaterial"); + + if (unlikely(lplpDirect3DMaterial == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DMaterial); + + D3DMATERIALHANDLE handle = m_commonD3DIntf->GetNextMaterialHandle(); + Com d3d5Material = new D3D5Material(nullptr, this, handle); + m_commonD3DIntf->EmplaceMaterial(d3d5Material->GetCommonMaterial(), handle); + + *lplpDirect3DMaterial = d3d5Material.ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Interface::CreateViewport(LPDIRECT3DVIEWPORT2 *lplpD3DViewport, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D5Interface::CreateViewport"); + + Com lplpD3DViewportProxy; + HRESULT hr = m_proxy->CreateViewport(&lplpD3DViewportProxy, pUnkOuter); + if (unlikely(FAILED(hr))) + return hr; + + InitReturnPtr(lplpD3DViewport); + + *lplpD3DViewport = ref(new D3D5Viewport(nullptr, std::move(lplpD3DViewportProxy), this)); + + return D3D_OK; + } + + // Minimal implementation which should suffice in most cases + HRESULT STDMETHODCALLTYPE D3D5Interface::FindDevice(D3DFINDDEVICESEARCH *lpD3DFDS, D3DFINDDEVICERESULT *lpD3DFDR) { + Logger::debug(">>> D3D5Interface::FindDevice"); + + if (unlikely(lpD3DFDS == nullptr || lpD3DFDR == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpD3DFDS->dwSize != sizeof(D3DFINDDEVICESEARCH))) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // Software emulation, this is expected to be exposed + D3DDEVICEDESC2 descRGB_HAL = GetD3D5Caps(IID_IDirect3DRGBDevice, d3dOptions); + D3DDEVICEDESC2 descRGB_HEL = descRGB_HAL; + descRGB_HAL.dwFlags = 0; + descRGB_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + descRGB_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + descRGB_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + descRGB_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + descRGB_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + + // Hardware acceleration + D3DDEVICEDESC2 descHAL_HAL = GetD3D5Caps(IID_IDirect3DHALDevice, d3dOptions); + D3DDEVICEDESC2 descHAL_HEL = descHAL_HAL; + descHAL_HEL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + descHAL_HEL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + descHAL_HEL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_POW2; + descHAL_HEL.dwDevCaps &= ~D3DDEVCAPS_HWTRANSFORMANDLIGHT + & ~D3DDEVCAPS_DRAWPRIMITIVES2 + & ~D3DDEVCAPS_DRAWPRIMITIVES2EX; + + D3DFINDDEVICERESULT2 lpD3DFRD2 = { }; + lpD3DFRD2.dwSize = sizeof(D3DFINDDEVICERESULT2); + + if (lpD3DFDS->dwFlags & D3DFDS_GUID) { + Logger::debug("D3D5Interface::FindDevice: Matching by device GUID"); + + if (lpD3DFDS->guid == IID_IDirect3DRGBDevice || + lpD3DFDS->guid == IID_IDirect3DMMXDevice || + lpD3DFDS->guid == IID_IDirect3DRampDevice) { + Logger::debug("D3D5Interface::FindDevice: Matched IID_IDirect3DRGBDevice"); + lpD3DFRD2.guid = IID_IDirect3DRGBDevice; + lpD3DFRD2.ddHwDesc = descRGB_HAL; + lpD3DFRD2.ddSwDesc = descRGB_HEL; + } else if (lpD3DFDS->guid == IID_IDirect3DHALDevice) { + Logger::debug("D3D5Interface::FindDevice: Matched IID_IDirect3DHALDevice"); + lpD3DFRD2.guid = IID_IDirect3DHALDevice; + lpD3DFRD2.ddHwDesc = descHAL_HAL; + lpD3DFRD2.ddSwDesc = descHAL_HEL; + } else { + Logger::err(str::format("D3D5Interface::FindDevice: Unknown device type: ", lpD3DFDS->guid)); + return DDERR_NOTFOUND; + } + + memcpy(lpD3DFDR, &lpD3DFRD2, sizeof(D3DFINDDEVICERESULT2)); + } else if (lpD3DFDS->dwFlags & D3DFDS_HARDWARE) { + Logger::debug("D3D5Interface::FindDevice: Matching by hardware flag"); + + if (likely(lpD3DFDS->bHardware == TRUE)) { + Logger::debug("D3D5Interface::FindDevice: Matched IID_IDirect3DHALDevice"); + lpD3DFRD2.guid = IID_IDirect3DHALDevice; + lpD3DFRD2.ddHwDesc = descHAL_HAL; + lpD3DFRD2.ddSwDesc = descHAL_HEL; + } else { + Logger::debug("D3D5Interface::FindDevice: Matched IID_IDirect3DRGBDevice"); + lpD3DFRD2.guid = IID_IDirect3DRGBDevice; + lpD3DFRD2.ddHwDesc = descRGB_HAL; + lpD3DFRD2.ddSwDesc = descRGB_HEL; + } + + memcpy(lpD3DFDR, &lpD3DFRD2, sizeof(D3DFINDDEVICERESULT2)); + } else { + Logger::err("D3D5Interface::FindDevice: Unhandled matching type"); + return DDERR_NOTFOUND; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Interface::CreateDevice(REFCLSID rclsid, LPDIRECTDRAWSURFACE lpDDS, LPDIRECT3DDEVICE2 *lplpD3DDevice) { + Logger::debug(">>> D3D5Interface::CreateDevice"); + + if (unlikely(lplpD3DDevice == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpD3DDevice); + + if (unlikely(lpDDS == nullptr)) { + Logger::err("D3D5Interface::CreateDevice: Null surface provided"); + return DDERR_INVALIDPARAMS; + } + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + DWORD deviceCreationFlags9 = D3DCREATE_SOFTWARE_VERTEXPROCESSING; + bool rgbFallback = false; + + if (likely(!d3dOptions->forceSWVP)) { + if (rclsid == IID_IDirect3DHALDevice) { + Logger::info("D3D5Interface::CreateDevice: Creating an IID_IDirect3DHALDevice device"); + deviceCreationFlags9 = D3DCREATE_MIXED_VERTEXPROCESSING; + } else if (rclsid == IID_IDirect3DRGBDevice) { + Logger::info("D3D5Interface::CreateDevice: Creating an IID_IDirect3DRGBDevice device"); + } else if (rclsid == IID_IDirect3DMMXDevice) { + Logger::warn("D3D5Interface::CreateDevice: Unsupported MMX device, falling back to RGB"); + rgbFallback = true; + } else if (rclsid == IID_IDirect3DRampDevice) { + Logger::warn("D3D5Interface::CreateDevice: Unsupported Ramp device, falling back to RGB"); + rgbFallback = true; + } else { + Logger::warn("D3D5Interface::CreateDevice: Unknown device identifier, falling back to RGB"); + Logger::warn(str::format(rclsid)); + rgbFallback = true; + } + } + + const IID rclsidOverride = rgbFallback ? IID_IDirect3DRGBDevice : rclsid; + + HWND hWnd = m_commonIntf->GetHWND(); + // Needed to sometimes safely skip intro playback on legacy devices + if (unlikely(hWnd == nullptr)) { + Logger::debug("D3D5Interface::CreateDevice: HWND is NULL"); + } + + Com rt; + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDS))) { + // Nightmare Creatures passes an IDirectDrawSurface3 surface as RT + if (unlikely(m_commonIntf->IsWrappedSurface(reinterpret_cast(lpDDS)))) { + Logger::debug("D3D5Interface::CreateDevice: IDirectDrawSurface3 surface passed as RT"); + DDraw3Surface* ddraw3Surface = reinterpret_cast(lpDDS); + // A DDrawSurface usually exists, because a DDraw3Surface is obtained from it via + // QueryInterface, however the passed surface can be obtained by GetAttachedSurface() calls + // on IDirectDrawSurface3, in which case it will NOT have a preexisting DDrawSurface + rt = ddraw3Surface->GetCommonSurface()->GetDDSurface(); + if (unlikely(rt == nullptr)) { + Com surface; + ddraw3Surface->GetProxied()->QueryInterface(__uuidof(IDirectDrawSurface), reinterpret_cast(&surface)); + rt = new DDrawSurface(ddraw3Surface->GetCommonSurface(), std::move(surface), + ddraw3Surface->GetCommonInterface()->GetDDInterface(), nullptr, false); + // Treat the new surface as the previously non-existent parent for our DDraw3Surface + ddraw3Surface->UpdateParent(rt.ptr()); + } + } else { + Logger::err("D3D5Interface::CreateDevice: Unwrapped surface passed as RT"); + return DDERR_GENERIC; + } + } else { + rt = static_cast(lpDDS); + } + + Com d3d5DeviceProxy; + HRESULT hr = m_proxy->CreateDevice(rclsidOverride, rt->GetProxied(), &d3d5DeviceProxy); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D5Interface::CreateDevice: Failed to create the proxy device"); + return hr; + } + + DDSURFACEDESC desc; + desc.dwSize = sizeof(DDSURFACEDESC); + lpDDS->GetSurfaceDesc(&desc); + + DWORD backBufferWidth = desc.dwWidth; + DWORD BackBufferHeight = desc.dwHeight; + + if (likely(!d3dOptions->forceProxiedPresent && + d3dOptions->backBufferResize)) { + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Ignore any mode size dimensions when in windowed present mode + if (exclusiveMode) { + DDrawModeSize* modeSize = m_commonIntf->GetModeSize(); + // Wayland apparently needs this for somewhat proper back buffer sizing + if ((modeSize->width && modeSize->width < desc.dwWidth) + || (modeSize->height && modeSize->height < desc.dwHeight)) { + Logger::info("D3D5Interface::CreateDevice: Enforcing mode dimensions"); + + backBufferWidth = modeSize->width; + BackBufferHeight = modeSize->height; + } + } + } + + d3d9::D3DFORMAT backBufferFormat = ConvertFormat(desc.ddpfPixelFormat); + + // Determine the supported AA sample count by querying the D3D9 interface + d3d9::D3DMULTISAMPLE_TYPE multiSampleType = d3d9::D3DMULTISAMPLE_NONE; + if (likely(d3dOptions->emulateFSAA != FSAAEmulation::Disabled)) { + HRESULT hr4S = m_d3d9->CheckDeviceMultiSampleType(0, d3d9::D3DDEVTYPE_HAL, backBufferFormat, + TRUE, d3d9::D3DMULTISAMPLE_4_SAMPLES, NULL); + if (unlikely(FAILED(hr4S))) { + HRESULT hr2S = m_d3d9->CheckDeviceMultiSampleType(0, d3d9::D3DDEVTYPE_HAL, backBufferFormat, + TRUE, d3d9::D3DMULTISAMPLE_2_SAMPLES, NULL); + if (unlikely(FAILED(hr2S))) { + Logger::warn("D3D5Interface::CreateDevice: No MSAA support has been detected"); + } else { + Logger::info("D3D5Interface::CreateDevice: Using 2x MSAA for FSAA emulation"); + multiSampleType = d3d9::D3DMULTISAMPLE_2_SAMPLES; + } + } else { + Logger::info("D3D5Interface::CreateDevice: Using 4x MSAA for FSAA emulation"); + multiSampleType = d3d9::D3DMULTISAMPLE_4_SAMPLES; + } + } else { + Logger::info("D3D5Interface::CreateDevice: FSAA emulation is disabled"); + } + + const DWORD cooperativeLevel = m_commonIntf->GetCooperativeLevel(); + + if ((cooperativeLevel & DDSCL_MULTITHREADED) || d3dOptions->forceMultiThreaded) { + Logger::info("D3D5Interface::CreateDevice: Using thread safe runtime synchronization"); + deviceCreationFlags9 |= D3DCREATE_MULTITHREADED; + } + // DDSCL_FPUPRESERVE does not exist prior to DDraw7, + // and DDSCL_FPUSETUP is NOT the default state + if (!(cooperativeLevel & DDSCL_FPUSETUP)) + deviceCreationFlags9 |= D3DCREATE_FPU_PRESERVE; + if (cooperativeLevel & DDSCL_NOWINDOWCHANGES) + deviceCreationFlags9 |= D3DCREATE_NOWINDOWCHANGES; + + Logger::info(str::format("D3D5Interface::CreateDevice: Back buffer size: ", desc.dwWidth, "x", desc.dwHeight)); + + DWORD backBufferCount = 0; + if (likely(!d3dOptions->forceSingleBackBuffer)) { + IDirectDrawSurface* backBuffer = rt->GetProxied(); + while (backBuffer != nullptr) { + IDirectDrawSurface* parentSurface = backBuffer; + backBuffer = nullptr; + parentSurface->EnumAttachedSurfaces(&backBuffer, ListBackBufferSurfacesCallback); + backBufferCount++; + // the swapchain will eventually return to its origin + if (backBuffer == rt->GetProxied()) + break; + } + } + // Consider the front buffer as well when reporting the overall count + Logger::info(str::format("D3D5Interface::CreateDevice: Back buffer count: ", backBufferCount + 1)); + + d3d9::D3DPRESENT_PARAMETERS params; + params.BackBufferWidth = backBufferWidth; + params.BackBufferHeight = BackBufferHeight; + params.BackBufferFormat = backBufferFormat; + params.BackBufferCount = backBufferCount; + params.MultiSampleType = multiSampleType; // Controlled through D3DRENDERSTATE_ANTIALIAS + params.MultiSampleQuality = 0; + params.SwapEffect = d3d9::D3DSWAPEFFECT_DISCARD; + params.hDeviceWindow = hWnd; + params.Windowed = TRUE; // Always use windowed, so that we can delegate mode switching to ddraw + params.EnableAutoDepthStencil = FALSE; + params.AutoDepthStencilFormat = d3d9::D3DFMT_UNKNOWN; + params.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; // Needed for back buffer locks + params.FullScreen_RefreshRateInHz = 0; // We'll get the right mode/refresh rate set by ddraw, just play along + params.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; // A D3D5 device always uses VSync + + Com device9; + hr = m_d3d9->CreateDevice( + D3DADAPTER_DEFAULT, + d3d9::D3DDEVTYPE_HAL, + hWnd, + deviceCreationFlags9, + ¶ms, + &device9 + ); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D5Interface::CreateDevice: Failed to create the D3D9 device"); + return hr; + } + + D3DDEVICEDESC2 desc5 = GetD3D5Caps(rclsidOverride, d3dOptions); + + try{ + Com device5 = new D3D5Device(std::move(d3d5DeviceProxy), this, desc5, + rclsidOverride, params, std::move(device9), + rt.ptr(), deviceCreationFlags9); + + // Set the newly created D3D5 device on the common interface + m_commonIntf->SetD3D5Device(device5.ptr()); + // Now that we have a valid D3D9 device pointer, we can initialize the depth stencil (if any) + device5->InitializeDS(); + + *lplpD3DDevice = device5.ref(); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + + return D3D_OK; + } + +} \ No newline at end of file diff --git a/src/ddraw/d3d5/d3d5_interface.h b/src/ddraw/d3d5/d3d5_interface.h new file mode 100644 index 00000000000..597b350ba4c --- /dev/null +++ b/src/ddraw/d3d5/d3d5_interface.h @@ -0,0 +1,69 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_options.h" +#include "../ddraw_util.h" +#include "../ddraw_format.h" + +#include "../ddraw_common_interface.h" +#include "../d3d_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +namespace dxvk { + + /** + * \brief D3D5 interface implementation + */ + class D3D5Interface final : public DDrawWrappedObject { + + public: + D3D5Interface( + DDrawCommonInterface* commonIntf, + D3DCommonInterface* commonD3DIntf, + Com&& d3d5Intf, + IUnknown* pParent); + + ~D3D5Interface(); + + ULONG STDMETHODCALLTYPE AddRef(); + + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE EnumDevices(LPD3DENUMDEVICESCALLBACK lpEnumDevicesCallback, LPVOID lpUserArg); + + HRESULT STDMETHODCALLTYPE CreateLight(LPDIRECT3DLIGHT *lplpDirect3DLight, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateMaterial(LPDIRECT3DMATERIAL2 *lplpDirect3DMaterial, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateViewport(LPDIRECT3DVIEWPORT2 *lplpD3DViewport, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE FindDevice(D3DFINDDEVICESEARCH *lpD3DFDS, D3DFINDDEVICERESULT *lpD3DFDR); + + HRESULT STDMETHODCALLTYPE CreateDevice(REFCLSID rclsid, LPDIRECTDRAWSURFACE lpDDS, LPDIRECT3DDEVICE2 *lplpD3DDevice); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + D3DCommonInterface* GetCommonD3DInterface() const { + return m_commonD3DIntf.ptr(); + } + + private: + + static uint32_t s_intfCount; + uint32_t m_intfCount = 0; + + Com m_bridge; + + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_commonD3DIntf; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d5/d3d5_material.cpp b/src/ddraw/d3d5/d3d5_material.cpp new file mode 100644 index 00000000000..a9dc4279670 --- /dev/null +++ b/src/ddraw/d3d5/d3d5_material.cpp @@ -0,0 +1,95 @@ +#include "d3d5_material.h" + +#include "d3d5_device.h" +#include "d3d5_interface.h" +#include "d3d5_viewport.h" + +#include "../ddraw/ddraw_interface.h" + +namespace dxvk { + + uint32_t D3D5Material::s_materialCount = 0; + + D3D5Material::D3D5Material( + Com&& proxyMaterial, + D3D5Interface* pParent, + D3DMATERIALHANDLE handle) + : DDrawWrappedObject(pParent, std::move(proxyMaterial), nullptr) { + m_commonMaterial = new D3DCommonMaterial(handle); + + m_materialCount = ++s_materialCount; + + Logger::debug(str::format("D3D5Material: Created a new material nr. [[2-", m_materialCount, "]]")); + } + + D3D5Material::~D3D5Material() { + m_parent->GetCommonD3DInterface()->ReleaseMaterialHandle(m_commonMaterial->GetMaterialHandle()); + + Logger::debug(str::format("D3D5Material: Material nr. [[2-", m_materialCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D5Material::SetMaterial(D3DMATERIAL *data) { + Logger::debug(">>> D3D5Material::SetMaterial"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + d3d9::D3DMATERIAL9* material9 = m_commonMaterial->GetD3D9Material(); + + material9->Diffuse = data->dcvDiffuse; + material9->Ambient = data->dcvAmbient; + material9->Specular = data->dcvSpecular; + material9->Emissive = data->dcvEmissive; + material9->Power = data->dvPower; + + D3DMATERIALHANDLE handle = m_commonMaterial->GetMaterialHandle(); + + Logger::debug(str::format(">>> D3D5Material::SetMaterial: Updated material nr. ", handle)); + Logger::debug(str::format(" Diffuse: ", material9->Diffuse.r, " ", material9->Diffuse.g, " ", material9->Diffuse.b)); + Logger::debug(str::format(" Ambient: ", material9->Ambient.r, " ", material9->Ambient.g, " ", material9->Ambient.b)); + Logger::debug(str::format(" Specular: ", material9->Specular.r, " ", material9->Specular.g, " ", material9->Specular.b)); + Logger::debug(str::format(" Emissive: ", material9->Emissive.r, " ", material9->Emissive.g, " ", material9->Emissive.b)); + Logger::debug(str::format(" Power: ", material9->Power)); + + // Update the D3D9 material directly if it's actively being used + D3D5Device* device5 = m_parent->GetCommonInterface()->GetD3D5Device(); + if (likely(device5 != nullptr)) { + D3DMATERIALHANDLE currentHandle = device5->GetCurrentMaterialHandle(); + if (currentHandle == handle) { + Logger::debug(str::format("D3D5Material::SetMaterial: Applying material nr. ", handle, " to D3D9")); + device5->GetD3D9()->SetMaterial(material9); + } + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Material::GetMaterial(D3DMATERIAL *data) { + Logger::debug(">>> D3D5Material::GetMaterial"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + d3d9::D3DMATERIAL9* material9 = m_commonMaterial->GetD3D9Material(); + + data->dcvDiffuse = material9->Diffuse; + data->dcvAmbient = material9->Ambient; + data->dcvSpecular = material9->Specular; + data->dcvEmissive = material9->Emissive; + data->dvPower = material9->Power; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Material::GetHandle(IDirect3DDevice2 *device, D3DMATERIALHANDLE *handle) { + Logger::debug(">>> D3D5Material::GetHandle"); + + if(unlikely(device == nullptr || handle == nullptr)) + return DDERR_INVALIDPARAMS; + + *handle = m_commonMaterial->GetMaterialHandle(); + + return D3D_OK; + } + +} diff --git a/src/ddraw/d3d5/d3d5_material.h b/src/ddraw/d3d5/d3d5_material.h new file mode 100644 index 00000000000..cff7ceed7ce --- /dev/null +++ b/src/ddraw/d3d5/d3d5_material.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../d3d_common_material.h" + +namespace dxvk { + + class D3D5Interface; + + class D3D5Material final : public DDrawWrappedObject { + + public: + + D3D5Material( + Com&& proxyMaterial, + D3D5Interface* pParent, + D3DMATERIALHANDLE handle); + + ~D3D5Material(); + + HRESULT STDMETHODCALLTYPE SetMaterial(D3DMATERIAL *data); + + HRESULT STDMETHODCALLTYPE GetMaterial(D3DMATERIAL *data); + + HRESULT STDMETHODCALLTYPE GetHandle(IDirect3DDevice2 *device, D3DMATERIALHANDLE *handle); + + D3DCommonMaterial* GetCommonMaterial() const { + return m_commonMaterial.ptr(); + } + + private: + + static uint32_t s_materialCount; + uint32_t m_materialCount = 0; + + Com m_commonMaterial; + + }; + +} diff --git a/src/ddraw/d3d5/d3d5_texture.cpp b/src/ddraw/d3d5/d3d5_texture.cpp new file mode 100644 index 00000000000..31b901cd28f --- /dev/null +++ b/src/ddraw/d3d5/d3d5_texture.cpp @@ -0,0 +1,124 @@ +#include "d3d5_texture.h" + +#include "d3d5_device.h" + +#include "../ddraw/ddraw_surface.h" + +namespace dxvk { + + uint32_t D3D5Texture::s_texCount = 0; + + D3D5Texture::D3D5Texture( + Com&& proxyTexture, + DDrawSurface* pParent, + D3DTEXTUREHANDLE handle) + : DDrawWrappedObject(pParent, std::move(proxyTexture), nullptr) { + m_commonTex = new D3DCommonTexture(m_parent->GetCommonSurface(), handle); + + m_texCount = ++s_texCount; + + Logger::debug(str::format("D3D5Texture: Created a new texture nr. [[2-", m_texCount, "]]")); + } + + D3D5Texture::~D3D5Texture() { + m_parent->GetCommonInterface()->ReleaseTextureHandle(m_commonTex->GetTextureHandle()); + + Logger::debug(str::format("D3D5Texture: Texture nr. [[2-", m_texCount, "]] bites the dust")); + } + + // Interlocked refcount with the parent IDirectDrawSurface + ULONG STDMETHODCALLTYPE D3D5Texture::AddRef() { + return m_parent->AddRef(); + } + + // Interlocked refcount with the parent IDirectDrawSurface + ULONG STDMETHODCALLTYPE D3D5Texture::Release() { + return m_parent->Release(); + } + + HRESULT STDMETHODCALLTYPE D3D5Texture::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> D3D5Texture::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(riid == __uuidof(IDirect3DTexture))) { + Logger::debug("D3D5Texture::QueryInterface: Query for IDirect3DTexture"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawGammaControl))) { + Logger::debug("D3D5Texture::QueryInterface: Query for IDirectDrawGammaControl"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawColorControl))) { + Logger::debug("D3D5Texture::QueryInterface: Query for IDirectDrawColorControl"); + return E_NOINTERFACE; + } + if (unlikely(riid == __uuidof(IUnknown) + || riid == __uuidof(IDirectDrawSurface))) { + Logger::debug("D3D5Texture::QueryInterface: Query for IDirectDrawSurface"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface2))) { + Logger::debug("D3D5Texture::QueryInterface: Query for IDirectDrawSurface2"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface3))) { + Logger::debug("D3D5Texture::QueryInterface: Query for IDirectDrawSurface3"); + return m_parent->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE D3D5Texture::GetHandle(LPDIRECT3DDEVICE2 lpDirect3DDevice2, LPD3DTEXTUREHANDLE lpHandle) { + Logger::debug(">>> D3D5Texture::GetHandle"); + + if(unlikely(lpDirect3DDevice2 == nullptr || lpHandle == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpHandle = m_commonTex->GetTextureHandle(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Texture::PaletteChanged(DWORD dwStart, DWORD dwCount) { + Logger::warn("<<< D3D5Texture::PaletteChanged: Proxy"); + return m_proxy->PaletteChanged(dwStart, dwCount); + } + + HRESULT STDMETHODCALLTYPE D3D5Texture::Load(LPDIRECT3DTEXTURE2 lpD3DTexture2) { + Logger::debug("<<< D3D5Texture::Load: Proxy"); + + Com d3d5Texture = static_cast(lpD3DTexture2); + + HRESULT hr = m_proxy->Load(d3d5Texture->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + // Update the cached parent surface desc + DDSURFACEDESC desc; + desc.dwSize = sizeof(DDSURFACEDESC); + HRESULT hrDesc = m_parent->GetProxied()->GetSurfaceDesc(&desc); + + if (unlikely(FAILED(hrDesc))) { + Logger::err("D3D5Texture::Load: Failed to retrieve updated surface desc"); + } else { + m_parent->GetCommonSurface()->SetDesc(desc); + } + + m_parent->GetCommonSurface()->DirtyMipMaps(); + + return hr; + } + +} diff --git a/src/ddraw/d3d5/d3d5_texture.h b/src/ddraw/d3d5/d3d5_texture.h new file mode 100644 index 00000000000..382fad268d6 --- /dev/null +++ b/src/ddraw/d3d5/d3d5_texture.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../d3d_common_texture.h" + +namespace dxvk { + + class DDrawSurface; + + class D3D5Texture final : public DDrawWrappedObject { + + public: + + D3D5Texture( + Com&& proxyTexture, + DDrawSurface* pParent, + D3DTEXTUREHANDLE handle); + + ~D3D5Texture(); + + ULONG STDMETHODCALLTYPE AddRef(); + + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE GetHandle(LPDIRECT3DDEVICE2 lpDirect3DDevice2, LPD3DTEXTUREHANDLE lpHandle); + + HRESULT STDMETHODCALLTYPE PaletteChanged(DWORD dwStart, DWORD dwCount); + + HRESULT STDMETHODCALLTYPE Load(LPDIRECT3DTEXTURE2 lpD3DTexture2); + + D3DCommonTexture* GetCommonTexture() const { + return m_commonTex.ptr(); + } + + private: + + static uint32_t s_texCount; + uint32_t m_texCount = 0; + + Com m_commonTex; + + }; + +} diff --git a/src/ddraw/d3d5/d3d5_viewport.cpp b/src/ddraw/d3d5/d3d5_viewport.cpp new file mode 100644 index 00000000000..89e685d5517 --- /dev/null +++ b/src/ddraw/d3d5/d3d5_viewport.cpp @@ -0,0 +1,509 @@ +#include "d3d5_viewport.h" + +#include "d3d5_device.h" + +#include "../d3d_light.h" +#include "../d3d_common_material.h" + +#include "../ddraw/ddraw_surface.h" + +#include "../d3d3/d3d3_viewport.h" +#include "../d3d6/d3d6_viewport.h" + +#include +#include + +namespace dxvk { + + uint32_t D3D5Viewport::s_viewportCount = 0; + + D3D5Viewport::D3D5Viewport( + D3DCommonViewport* commonViewport, + Com&& proxyViewport, + D3D5Interface* pParent) + : DDrawWrappedObject(pParent, std::move(proxyViewport), nullptr) + , m_commonViewport ( commonViewport ) { + + if (m_commonViewport == nullptr) + m_commonViewport = new D3DCommonViewport(m_parent->GetCommonD3DInterface()); + + m_commonViewport->SetD3D5Viewport(this); + + m_viewportCount = ++s_viewportCount; + + Logger::debug(str::format("D3D5Viewport: Created a new viewport nr. [[2-", m_viewportCount, "]]")); + } + + D3D5Viewport::~D3D5Viewport() { + std::vector>& lights = m_commonViewport->GetLights(); + + // Dissasociate every bound light from this viewport + for (auto light : lights) { + light->SetViewport5(nullptr); + } + + m_commonViewport->SetD3D5Viewport(nullptr); + + Logger::debug(str::format("D3D5Viewport: Viewport nr. [[2-", m_viewportCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> D3D5Viewport::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + // Some games query for legacy viewport interfaces + if (unlikely(riid == __uuidof(IDirect3DViewport))) { + if (m_commonViewport->GetD3D3Viewport() != nullptr) { + Logger::debug("D3D6Viewport::QueryInterface: Query for existing IDirect3DViewport"); + return m_commonViewport->GetD3D3Viewport()->QueryInterface(riid, ppvObject); + } + + Logger::debug("D3D5Viewport::QueryInterface: Query for IDirect3DViewport"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new D3D3Viewport(m_commonViewport.ptr(), std::move(ppvProxyObject), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirect3DViewport3))) { + if (m_commonViewport->GetD3D6Viewport() != nullptr) { + Logger::debug("D3D6Viewport::QueryInterface: Query for existing IDirect3DViewport3"); + return m_commonViewport->GetD3D6Viewport()->QueryInterface(riid, ppvObject); + } + + Logger::debug("D3D5Viewport::QueryInterface: Query for IDirect3DViewport3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new D3D6Viewport(m_commonViewport.ptr(), std::move(ppvProxyObject), nullptr)); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // Docs state: "The IDirect3DViewport2::Initialize method is not implemented." + HRESULT STDMETHODCALLTYPE D3D5Viewport::Initialize(LPDIRECT3D lpDirect3D) { + Logger::debug(">>> D3D5Viewport::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::GetViewport(D3DVIEWPORT *data) { + Logger::debug(">>> D3D5Viewport::GetViewport"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->IsViewportSet())) + return D3DERR_VIEWPORTDATANOTSET; + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + data->dwX = viewport9->X; + data->dwY = viewport9->Y; + data->dwWidth = viewport9->Width; + data->dwHeight = viewport9->Height; + data->dvMinZ = viewport9->MinZ; + data->dvMaxZ = viewport9->MaxZ; + + data->dvMaxX = 1.0f; + data->dvMaxY = 1.0f; + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + data->dvScaleX = legacyScale->x * (float)data->dwWidth / 2.0f; + data->dvScaleY = legacyScale->y * (float)data->dwHeight / 2.0f; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::SetViewport(D3DVIEWPORT *data) { + Logger::debug(">>> D3D5Viewport::SetViewport"); + + HRESULT hr = m_proxy->SetViewport(data); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + // TODO: Check viewport dimensions against the currently set RT, + // and perform some sanity checks (positive, non-zero dimensions) + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + // The docs state: "The method ignores the values in the dvMaxX, dvMaxY, + // dvMinZ, and dvMaxZ members.", which appears correct. + viewport9->X = data->dwX; + viewport9->Y = data->dwY; + viewport9->Width = data->dwWidth; + viewport9->Height = data->dwHeight; + viewport9->MinZ = 0.0f; + viewport9->MaxZ = 1.0f; + + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + legacyScale->x = 2.0f * data->dvScaleX / (float)data->dwWidth; + legacyScale->y = 2.0f * data->dvScaleY / (float)data->dwHeight; + legacyScale->z = 1.0f; + D3DVECTOR* legacyClip = m_commonViewport->GetLegacyClip(); + legacyClip->x = 0.0f; + legacyClip->y = 0.0f; + legacyClip->z = 0.0f; + + m_commonViewport->MarkViewportAsSet(); + + if (m_commonViewport->IsCurrentViewport()) + ApplyViewport(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::TransformVertices(DWORD vertex_count, D3DTRANSFORMDATA *data, DWORD flags, DWORD *offscreen) { + Logger::debug("<<< D3D5Viewport::TransformVertices: Proxy"); + return m_proxy->TransformVertices(vertex_count, data, flags, offscreen); + } + + // Docs state: "The IDirect3DViewport2::LightElements method is not currently implemented." + HRESULT STDMETHODCALLTYPE D3D5Viewport::LightElements(DWORD element_count, D3DLIGHTDATA *data) { + Logger::warn(">>> D3D5Viewport::LightElements"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::SetBackground(D3DMATERIALHANDLE hMat) { + Logger::debug(">>> D3D5Viewport::SetBackground"); + + if (unlikely(m_commonViewport->GetMaterialHandle() == hMat)) + return D3D_OK; + + D3DCommonMaterial* commonMaterial = m_commonViewport->GetCommonD3DInterface()->GetCommonMaterialFromHandle(hMat); + + if (unlikely(commonMaterial == nullptr)) + return DDERR_INVALIDPARAMS; + + m_commonViewport->MarkMaterialAsSet(); + + // Cache only the set material handle, as its color can + // change after it is set (get it on Clear directly) + m_commonViewport->SetMaterialHandle(hMat); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::GetBackground(D3DMATERIALHANDLE *material, BOOL *valid) { + Logger::debug(">>> D3D5Viewport::GetBackground"); + + if (unlikely(material == nullptr || valid == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(m_commonViewport->IsMaterialSet())) + *material = m_commonViewport->GetMaterialHandle(); + *valid = m_commonViewport->IsMaterialSet(); + + return D3D_OK; + } + + // One could speculate this was meant to set a z-buffer depth value + // to be used during clears, perhaps, similarly to SetBackground(), + // however it has not seen any practical use in the wild + HRESULT STDMETHODCALLTYPE D3D5Viewport::SetBackgroundDepth(IDirectDrawSurface *surface) { + Logger::warn("!!! D3D5Viewport::SetBackgroundDepth: Stub"); + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::GetBackgroundDepth(IDirectDrawSurface **surface, BOOL *valid) { + Logger::warn("!!! D3D5Viewport::SetBackgroundDepth: Stub"); + + if (unlikely(surface == nullptr || valid == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(surface); + + *valid = FALSE; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::Clear(DWORD count, D3DRECT *rects, DWORD flags) { + Logger::debug("<<< D3D5Viewport::Clear: Proxy"); + + // Fast skip + if (unlikely(!count && rects)) + return D3D_OK; + + HRESULT hr = m_proxy->Clear(count, rects, flags); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonViewport->GetD3D9Device(); + + // Temporarily activate this viewport in order to clear it + d3d9::D3DVIEWPORT9 currentViewport9; + if (!m_commonViewport->IsCurrentViewport()) { + D3D5Viewport* currentViewport = m_commonViewport->GetCurrentD3D5Viewport(); + if (currentViewport != nullptr) { + currentViewport9 = *currentViewport->GetCommonViewport()->GetD3D9Viewport(); + } else { + d3d9Device->GetViewport(¤tViewport9); + } + d3d9Device->SetViewport(m_commonViewport->GetD3D9Viewport()); + } + + static constexpr D3DCOLOR defaultColor = D3DCOLOR_RGBA(0, 0, 0, 0); + D3DMATERIALHANDLE handle = m_commonViewport->GetMaterialHandle(); + D3DCommonMaterial* commonMaterial = m_commonViewport->GetCommonD3DInterface()->GetCommonMaterialFromHandle(handle); + D3DCOLOR clearColor = commonMaterial != nullptr ? commonMaterial->GetMaterialColor() : defaultColor; + + HRESULT hr9 = d3d9Device->Clear(count, rects, flags, clearColor, 1.0f, 0u); + if (unlikely(FAILED(hr9))) + Logger::err("D3D5Viewport::Clear: Failed D3D9 Clear call"); + + // Restore the previously active viewport + if (!m_commonViewport->IsCurrentViewport()) { + d3d9Device->SetViewport(¤tViewport9); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::AddLight(IDirect3DLight *light) { + Logger::debug(">>> D3D5Viewport::AddLight"); + + if (unlikely(light == nullptr)) + return DDERR_INVALIDPARAMS; + + D3DLight* d3dLight = reinterpret_cast(light); + + if (unlikely(d3dLight->HasViewport())) + return D3DERR_LIGHTHASVIEWPORT; + + if (m_commonViewport->HasDevice()) { + Logger::debug("D3D5Viewport::AddLight: Enabling device legacy light model"); + m_commonViewport->EnableLegacyLights(d3dLight->IsD3DLight2()); + } + + std::vector>& lights = m_commonViewport->GetLights(); + // No need to check if the light is already attached, since + // if that's the case it will have a set viewport above + lights.push_back(d3dLight); + d3dLight->SetViewport5(this); + + if (m_commonViewport->HasDevice() && m_commonViewport->IsCurrentViewport()) + ApplyAndActivateLight(d3dLight->GetIndex(), d3dLight); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::DeleteLight(IDirect3DLight *light) { + Logger::debug(">>> D3D5Viewport::DeleteLight"); + + if (unlikely(light == nullptr)) + return DDERR_INVALIDPARAMS; + + D3DLight* d3dLight = reinterpret_cast(light); + + if (unlikely(!d3dLight->HasViewport())) + return DDERR_INVALIDPARAMS; + + std::vector>& lights = m_commonViewport->GetLights(); + + auto it = std::find(lights.begin(), lights.end(), d3dLight); + if (likely(it != lights.end())) { + const DWORD lightIndex = d3dLight->GetIndex(); + if (m_commonViewport->HasDevice() && m_commonViewport->IsCurrentViewport() && d3dLight->IsActive()) { + Logger::debug(str::format("D3D5Viewport: Disabling light nr. ", lightIndex)); + m_commonViewport->GetD3D9Device()->LightEnable(lightIndex, FALSE); + } + lights.erase(it); + d3dLight->SetViewport5(nullptr); + } else { + Logger::warn("D3D5Viewport::DeleteLight: Light not found"); + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::NextLight(IDirect3DLight *lpDirect3DLight, IDirect3DLight **lplpDirect3DLight, DWORD flags) { + Logger::debug(">>> D3D5Viewport::NextLight"); + + if (unlikely(lplpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DLight); + + std::vector>& lights = m_commonViewport->GetLights(); + + if (flags & D3DNEXT_HEAD) { + if (likely(lights.size() > 0)) + *lplpDirect3DLight = lights.front().ref(); + } else if (flags & D3DNEXT_NEXT) { + if (unlikely(lpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(lights.size() > 0)) + Logger::warn("D3D5Viewport::NextLight: Unimplemented D3DNEXT_NEXT flag"); + } else if (flags & D3DNEXT_TAIL) { + if (likely(lights.size() > 0)) + *lplpDirect3DLight = lights.back().ref(); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::GetViewport2(D3DVIEWPORT2 *data) { + Logger::debug(">>> D3D5Viewport::GetViewport2"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT2))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->IsViewportSet())) + return D3DERR_VIEWPORTDATANOTSET; + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + data->dwX = viewport9->X; + data->dwY = viewport9->Y; + data->dwWidth = viewport9->Width; + data->dwHeight = viewport9->Height; + data->dvMinZ = viewport9->MinZ; + data->dvMaxZ = viewport9->MaxZ; + + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + data->dvClipWidth = 2.0f / legacyScale->x; + data->dvClipHeight = 2.0f / legacyScale->y; + D3DVECTOR* legacyClip = m_commonViewport->GetLegacyClip(); + data->dvClipX = data->dvClipWidth * (legacyClip->x + 1.0f) / -2.0f; + data->dvClipY = data->dvClipHeight * (legacyClip->y - 1.0f) / -2.0f; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D5Viewport::SetViewport2(D3DVIEWPORT2 *data) { + Logger::debug(">>> D3D5Viewport::SetViewport2"); + + HRESULT hr = m_proxy->SetViewport2(data); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT2))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + // TODO: Check viewport dimensions against the currently set RT, + // and perform some sanity checks (positive, non-zero dimensions) + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + viewport9->X = data->dwX; + viewport9->Y = data->dwY; + viewport9->Width = data->dwWidth; + viewport9->Height = data->dwHeight; + viewport9->MinZ = 0.0f; + viewport9->MaxZ = 1.0f; + + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + legacyScale->x = 2.0f / data->dvClipWidth; + legacyScale->y = 2.0f / data->dvClipHeight; + legacyScale->z = 1.0f / (data->dvMaxZ - data->dvMinZ); + D3DVECTOR* legacyClip = m_commonViewport->GetLegacyClip(); + legacyClip->x = -2.0f * data->dvClipX / data->dvClipWidth - 1.0f; + legacyClip->y = -2.0f * data->dvClipY / data->dvClipHeight + 1.0f; + legacyClip->z = -data->dvMinZ / (data->dvMaxZ - data->dvMinZ); + + m_commonViewport->MarkViewportAsSet(); + + if (m_commonViewport->IsCurrentViewport()) + ApplyViewport(); + + return D3D_OK; + } + + HRESULT D3D5Viewport::ApplyViewport() { + if (!m_commonViewport->IsViewportSet()) + return D3D_OK; + + Logger::debug("D3D5Viewport: Applying viewport to D3D9"); + + HRESULT hr = m_commonViewport->GetD3D9Device()->SetViewport(m_commonViewport->GetD3D9Viewport()); + if(unlikely(FAILED(hr))) + Logger::err("D3D5Viewport: Failed to set the D3D9 viewport"); + + return hr; + } + + HRESULT D3D5Viewport::ApplyAndActivateLights() { + std::vector>& lights = m_commonViewport->GetLights(); + + if (!lights.size()) + return D3D_OK; + + Logger::debug("D3D5Viewport: Applying lights to D3D9"); + + for (auto light: lights) + ApplyAndActivateLight(light->GetIndex(), light.ptr()); + + return D3D_OK; + } + + HRESULT D3D5Viewport::ApplyAndActivateLight(DWORD index, D3DLight* light) { + d3d9::IDirect3DDevice9* d3d9Device = m_commonViewport->GetD3D9Device(); + + HRESULT hr = d3d9Device->SetLight(index, light->GetD3D9Light()); + if (unlikely(FAILED(hr))) { + Logger::err("D3D5Viewport: Failed D3D9 SetLight call"); + } else { + HRESULT hrLE; + if (light->IsActive()) { + Logger::debug(str::format("D3D5Viewport: Enabling light nr. ", index)); + hrLE = d3d9Device->LightEnable(index, TRUE); + if (unlikely(FAILED(hrLE))) + Logger::err("D3D5Viewport: Failed D3D9 LightEnable call (TRUE)"); + } else { + Logger::debug(str::format("D3D5Viewport: Disabling light nr. ", index)); + hrLE = d3d9Device->LightEnable(index, FALSE); + if (unlikely(FAILED(hrLE))) + Logger::err("D3D5Viewport: Failed D3D9 LightEnable call (FALSE)"); + } + } + + return hr; + } + +} diff --git a/src/ddraw/d3d5/d3d5_viewport.h b/src/ddraw/d3d5/d3d5_viewport.h new file mode 100644 index 00000000000..6b9248c82c5 --- /dev/null +++ b/src/ddraw/d3d5/d3d5_viewport.h @@ -0,0 +1,77 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_util.h" + +#include "../d3d_common_viewport.h" + +#include "d3d5_interface.h" + +namespace dxvk { + + class D3DLight; + + class D3D5Viewport final : public DDrawWrappedObject { + + public: + + D3D5Viewport( + D3DCommonViewport* commonViewport, + Com&& proxyViewport, + D3D5Interface* pParent); + + ~D3D5Viewport(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECT3D lpDirect3D); + + HRESULT STDMETHODCALLTYPE GetViewport(D3DVIEWPORT *data); + + HRESULT STDMETHODCALLTYPE SetViewport(D3DVIEWPORT *data); + + HRESULT STDMETHODCALLTYPE TransformVertices(DWORD vertex_count, D3DTRANSFORMDATA *data, DWORD flags, DWORD *offscreen); + + HRESULT STDMETHODCALLTYPE LightElements(DWORD element_count, D3DLIGHTDATA *data); + + HRESULT STDMETHODCALLTYPE SetBackground(D3DMATERIALHANDLE hMat); + + HRESULT STDMETHODCALLTYPE GetBackground(D3DMATERIALHANDLE *material, BOOL *valid); + + HRESULT STDMETHODCALLTYPE SetBackgroundDepth(IDirectDrawSurface *surface); + + HRESULT STDMETHODCALLTYPE GetBackgroundDepth(IDirectDrawSurface **surface, BOOL *valid); + + HRESULT STDMETHODCALLTYPE Clear(DWORD count, D3DRECT *rects, DWORD flags); + + HRESULT STDMETHODCALLTYPE AddLight(IDirect3DLight *light); + + HRESULT STDMETHODCALLTYPE DeleteLight(IDirect3DLight *light); + + HRESULT STDMETHODCALLTYPE NextLight(IDirect3DLight *lpDirect3DLight, IDirect3DLight **lplpDirect3DLight, DWORD flags); + + HRESULT STDMETHODCALLTYPE GetViewport2(D3DVIEWPORT2 *data); + + HRESULT STDMETHODCALLTYPE SetViewport2(D3DVIEWPORT2 *data); + + HRESULT ApplyViewport(); + + HRESULT ApplyAndActivateLights(); + + HRESULT ApplyAndActivateLight(DWORD index, D3DLight* light); + + D3DCommonViewport* GetCommonViewport() const { + return m_commonViewport.ptr(); + } + + private: + + static uint32_t s_viewportCount; + uint32_t m_viewportCount = 0; + + Com m_commonViewport; + + }; + +} diff --git a/src/ddraw/d3d6/d3d6_buffer.cpp b/src/ddraw/d3d6/d3d6_buffer.cpp new file mode 100644 index 00000000000..2c7f943ba48 --- /dev/null +++ b/src/ddraw/d3d6/d3d6_buffer.cpp @@ -0,0 +1,194 @@ +#include "d3d6_buffer.h" + +#include "../ddraw_util.h" + +#include "../d3d_multithread.h" + +#include "../ddraw4/ddraw4_interface.h" + +namespace dxvk { + + uint32_t D3D6VertexBuffer::s_buffCount = 0; + + D3D6VertexBuffer::D3D6VertexBuffer( + Com&& buffProxy, + Com&& pBuffer9, + D3D6Interface* pParent, + DWORD creationFlags, + D3DVERTEXBUFFERDESC desc) + : DDrawWrappedObject(pParent, std::move(buffProxy), std::move(pBuffer9)) + , m_commonIntf ( pParent->GetCommonInterface() ) + , m_creationFlags ( creationFlags ) + , m_desc ( desc ) + , m_stride ( GetFVFSize(desc.dwFVF) ) + , m_size ( m_stride * desc.dwNumVertices ) { + m_buffCount = ++s_buffCount; + + ListBufferDetails(); + } + + D3D6VertexBuffer::~D3D6VertexBuffer() { + Logger::debug(str::format("D3D6VertexBuffer: Buffer nr. {{1-", m_buffCount, "}} bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D6VertexBuffer::GetVertexBufferDesc(LPD3DVERTEXBUFFERDESC lpVBDesc) { + Logger::debug(">>> D3D6VertexBuffer::GetVertexBufferDesc"); + + if (unlikely(lpVBDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + const DWORD dwSize = lpVBDesc->dwSize; + + *lpVBDesc = m_desc; + // The value passed in dwSize during the query is expected to be + // preserved, even if it is not equal to sizeof(D3DVERTEXBUFFERDESC) + lpVBDesc->dwSize = dwSize; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6VertexBuffer::Lock(DWORD flags, void **data, DWORD *data_size) { + Logger::debug(">>> D3D6VertexBuffer::Lock"); + + if (unlikely(IsOptimized())) + return D3DERR_VERTEXBUFFEROPTIMIZED; + + RefreshD3D6Device(); + if (unlikely(!IsInitialized())) { + HRESULT hrInit = InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + if (data_size != nullptr) + *data_size = m_size; + + HRESULT hr = m_d3d9->Lock(0, 0, data, ConvertD3D6LockFlags(flags, false)); + + if (likely(SUCCEEDED(hr))) + m_locked = true; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6VertexBuffer::Unlock() { + Logger::debug(">>> D3D6VertexBuffer::Unlock"); + + RefreshD3D6Device(); + if (unlikely(!IsInitialized())) { + HRESULT hrInit = InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + HRESULT hr = m_d3d9->Unlock(); + + if (likely(SUCCEEDED(hr))) + m_locked = false; + else + return D3DERR_VERTEXBUFFERUNLOCKFAILED; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6VertexBuffer::ProcessVertices(DWORD dwVertexOp, DWORD dwDestIndex, DWORD dwCount, LPDIRECT3DVERTEXBUFFER lpSrcBuffer, DWORD dwSrcIndex, LPDIRECT3DDEVICE3 lpD3DDevice, DWORD dwFlags) { + Logger::debug(">>> D3D6VertexBuffer::ProcessVertices"); + + if (unlikely(!dwCount)) + return D3D_OK; + + if (unlikely(lpD3DDevice == nullptr || lpSrcBuffer == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!(dwVertexOp & D3DVOP_TRANSFORM))) + return DDERR_INVALIDPARAMS; + + D3D6Device* device = static_cast(lpD3DDevice); + D3D6VertexBuffer* vb = static_cast(lpSrcBuffer); + + vb->RefreshD3D6Device(); + if (unlikely(vb->GetDevice() == nullptr || device != vb->GetDevice())) { + Logger::err("D3D6VertexBuffer::ProcessVertices: Incompatible or null device"); + return DDERR_GENERIC; + } + + HRESULT hrInit; + + // Check and initialize the source buffer + if (unlikely(!vb->IsInitialized())) { + hrInit = vb->InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + // Check and initialize the destination buffer (this buffer) + RefreshD3D6Device(); + if (unlikely(!IsInitialized())) { + hrInit = InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + D3DDeviceLock lock = device->LockDevice(); + + HandlePreProcessVerticesFlags(dwVertexOp); + + device->GetD3D9()->SetFVF(m_desc.dwFVF); + device->GetD3D9()->SetStreamSource(0, vb->GetD3D9(), 0, vb->GetStride()); + HRESULT hr = device->GetD3D9()->ProcessVertices(dwSrcIndex, dwDestIndex, dwCount, m_d3d9.ptr(), nullptr, dwFlags); + if (unlikely(FAILED(hr))) { + Logger::err("D3D6VertexBuffer::ProcessVertices: Failed call to D3D9 ProcessVertices"); + } + + HandlePostProcessVerticesFlags(dwVertexOp); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6VertexBuffer::Optimize(LPDIRECT3DDEVICE3 lpD3DDevice, DWORD dwFlags) { + Logger::debug(">>> D3D6VertexBuffer::Optimize"); + + if (unlikely(lpD3DDevice == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(IsLocked())) + return D3DERR_VERTEXBUFFERLOCKED; + + if (unlikely(IsOptimized())) + return D3DERR_VERTEXBUFFEROPTIMIZED; + + m_desc.dwCaps &= D3DVBCAPS_OPTIMIZED; + + return D3D_OK; + }; + + HRESULT D3D6VertexBuffer::InitializeD3D9() { + // Can't create anything without a valid device + if (unlikely(m_d3d6Device == nullptr)) { + Logger::warn("D3D6VertexBuffer::InitializeD3D9: Null D3D6 device, can't initialize right now"); + return DDERR_GENERIC; + } + + d3d9::D3DPOOL pool = d3d9::D3DPOOL_DEFAULT; + + if (m_desc.dwCaps & D3DVBCAPS_SYSTEMMEMORY) + pool = d3d9::D3DPOOL_SYSTEMMEM; + + const char* poolPlacement = pool == d3d9::D3DPOOL_DEFAULT ? "D3DPOOL_DEFAULT" : "D3DPOOL_SYSTEMMEM"; + + Logger::debug(str::format("D3D6VertexBuffer::InitializeD3D9: Placing in: ", poolPlacement)); + + const DWORD usage = ConvertD3D6UsageFlags(m_desc.dwCaps, m_creationFlags); + HRESULT hr = m_d3d6Device->GetD3D9()->CreateVertexBuffer(m_size, usage, m_desc.dwFVF, pool, &m_d3d9, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D6VertexBuffer::InitializeD3D9: Failed to create D3D9 vertex buffer"); + return hr; + } + + Logger::debug("D3D6VertexBuffer::InitializeD3D9: Created D3D9 vertex buffer"); + + return DD_OK; + } + +} diff --git a/src/ddraw/d3d6/d3d6_buffer.h b/src/ddraw/d3d6/d3d6_buffer.h new file mode 100644 index 00000000000..fcd39a07ecf --- /dev/null +++ b/src/ddraw/d3d6/d3d6_buffer.h @@ -0,0 +1,120 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../ddraw_common_interface.h" + +#include "d3d6_interface.h" +#include "d3d6_device.h" + +namespace dxvk { + + class D3D6VertexBuffer final : public DDrawWrappedObject { + + public: + + D3D6VertexBuffer( + Com&& buffProxy, + Com&& pBuffer9, + D3D6Interface* pParent, + DWORD creationFlags, + D3DVERTEXBUFFERDESC desc); + + ~D3D6VertexBuffer(); + + HRESULT STDMETHODCALLTYPE GetVertexBufferDesc(LPD3DVERTEXBUFFERDESC lpVBDesc); + + HRESULT STDMETHODCALLTYPE Lock(DWORD dwFlags, LPVOID* lplpData, LPDWORD lpdwSize); + + HRESULT STDMETHODCALLTYPE Unlock(); + + HRESULT STDMETHODCALLTYPE ProcessVertices(DWORD dwVertexOp, DWORD dwDestIndex, DWORD dwCount, LPDIRECT3DVERTEXBUFFER lpSrcBuffer, DWORD dwSrcIndex, LPDIRECT3DDEVICE3 lpD3DDevice, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE Optimize(LPDIRECT3DDEVICE3 lpD3DDevice, DWORD dwFlags); + + DWORD GetFVF() const { + return m_desc.dwFVF; + } + + DWORD GetStride() const { + return m_stride; + } + + DWORD GetNumVertices() const { + return m_size / m_stride; + } + + bool IsLocked() const { + return m_locked; + } + + D3D6Device* GetDevice() const { + return m_d3d6Device; + } + + void RefreshD3D6Device() { + D3D6Device* d3d6Device = m_commonIntf->GetD3D6Device(); + if (unlikely(m_d3d6Device != d3d6Device)) { + // Check if the device has been recreated and reset all D3D9 resources + if (unlikely(m_d3d6Device != nullptr)) { + Logger::debug("D3D6VertexBuffer::RefreshD3D6Device: Device context has changed, clearing D3D9 buffers"); + m_d3d9 = nullptr; + } + m_d3d6Device = d3d6Device; + } + } + + HRESULT InitializeD3D9(); + + private: + + inline bool IsOptimized() const { + return m_desc.dwCaps & D3DVBCAPS_OPTIMIZED; + } + + inline void HandlePreProcessVerticesFlags(DWORD pvFlags) { + // Disable lighting if the D3DVOP_LIGHT isn't specified + if (!(pvFlags & D3DVOP_LIGHT)) { + m_d3d6Device->GetD3D9()->GetRenderState(d3d9::D3DRS_LIGHTING, &m_lighting); + if (m_lighting) { + //Logger::debug("D3D6VertexBuffer: Disabling lighting"); + m_d3d6Device->GetD3D9()->SetRenderState(d3d9::D3DRS_LIGHTING, FALSE); + } + } + } + + inline void HandlePostProcessVerticesFlags(DWORD pvFlags) { + if (!(pvFlags & D3DVOP_LIGHT) && m_lighting) { + //Logger::debug("D3D6VertexBuffer: Enabling lighting"); + m_d3d6Device->GetD3D9()->SetRenderState(d3d9::D3DRS_LIGHTING, TRUE); + } + } + + inline void ListBufferDetails() const { + Logger::debug(str::format("D3D6VertexBuffer: Created a new buffer nr. {{1-", m_buffCount, "}}:")); + Logger::debug(str::format(" Size: ", m_size)); + Logger::debug(str::format(" FVF: ", m_desc.dwFVF)); + Logger::debug(str::format(" Vertices: ", m_size / m_stride)); + } + + bool m_locked = false; + + static uint32_t s_buffCount; + uint32_t m_buffCount = 0; + + DDrawCommonInterface* m_commonIntf = nullptr; + + D3D6Device* m_d3d6Device = nullptr; + + DWORD m_lighting = FALSE; + + DWORD m_creationFlags = 0; + D3DVERTEXBUFFERDESC m_desc; + + UINT m_stride = 0; + UINT m_size = 0; + + }; + +} diff --git a/src/ddraw/d3d6/d3d6_device.cpp b/src/ddraw/d3d6/d3d6_device.cpp new file mode 100644 index 00000000000..b39f4fe34fb --- /dev/null +++ b/src/ddraw/d3d6/d3d6_device.cpp @@ -0,0 +1,1851 @@ +#include "d3d6_device.h" + +#include "d3d6_buffer.h" +#include "d3d6_texture.h" + +#include "../ddraw4/ddraw4_surface.h" + +#include +#include +#include "../../util/util_bit.h" + +namespace dxvk { + + uint32_t D3D6Device::s_deviceCount = 0; + + D3D6Device::D3D6Device( + Com&& d3d6DeviceProxy, + D3D6Interface* pParent, + D3DDEVICEDESC Desc, + GUID deviceGUID, + d3d9::D3DPRESENT_PARAMETERS Params9, + Com&& pDevice9, + DDraw4Surface* pSurface, + DWORD CreationFlags9) + : DDrawWrappedObject(pParent, std::move(d3d6DeviceProxy), std::move(pDevice9)) + , m_commonIntf ( pParent->GetCommonInterface() ) + , m_multithread ( CreationFlags9 & D3DCREATE_MULTITHREADED ) + , m_params9 ( Params9 ) + , m_desc ( Desc ) + , m_deviceGUID ( deviceGUID ) + , m_rt ( pSurface ) { + // Get the bridge interface to D3D9 + if (unlikely(FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8Bridge), reinterpret_cast(&m_bridge))))) { + throw DxvkError("D3D6Device: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + // Common D3D9 index buffers + if (unlikely(FAILED(InitializeIndexBuffers()))) { + throw DxvkError("D3D6Device: ERROR! Failed to initialize D3D9 index buffers."); + } + + m_totalMemory = m_bridge->DetermineInitialTextureMemory(); + + m_textures.fill(nullptr); + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->emulateFSAA == FSAAEmulation::Forced)) { + Logger::warn("D3D6Device: Force enabling AA"); + m_d3d9->SetRenderState(d3d9::D3DRS_MULTISAMPLEANTIALIAS, TRUE); + } + + // The default value of D3DRENDERSTATE_TEXTUREMAPBLEND in D3D6 is D3DTBLEND_MODULATE + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + + m_deviceCount = ++s_deviceCount; + + Logger::debug(str::format("D3D6Device: Created a new device nr. ((3-", m_deviceCount, "))")); + } + + D3D6Device::~D3D6Device() { + if (LogIndexBufferUsageStats()) { + Logger::info("D3D6Device: Index buffer upload statistics:"); + Logger::info(str::format(" XXS: ", m_ib9_uploads[0])); + Logger::info(str::format(" XS : ", m_ib9_uploads[1])); + Logger::info(str::format(" S : ", m_ib9_uploads[2])); + Logger::info(str::format(" M : ", m_ib9_uploads[3])); + Logger::info(str::format(" L : ", m_ib9_uploads[4])); + Logger::info(str::format(" XL : ", m_ib9_uploads[5])); + Logger::info(str::format(" XXL: ", m_ib9_uploads[6])); + } + + // Dissasociate every bound viewport from this device + for (auto viewport : m_viewports) { + viewport->GetCommonViewport()->SetD3D6Device(nullptr); + } + + // Clear the common interface device pointer if it points to this device + if (m_commonIntf->GetD3D6Device() == this) + m_commonIntf->SetD3D6Device(nullptr); + + Logger::debug(str::format("D3D6Device: Device nr. ((3-", m_deviceCount, ")) bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetCaps(D3DDEVICEDESC *hal_desc, D3DDEVICEDESC *hel_desc) { + Logger::debug(">>> D3D6Device::GetCaps"); + + if (unlikely(hal_desc == nullptr || hel_desc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!IsValidD3DDeviceDescSize(hal_desc->dwSize) + || !IsValidD3DDeviceDescSize(hel_desc->dwSize))) + return DDERR_INVALIDPARAMS; + + D3DDEVICEDESC desc_HAL = m_desc; + D3DDEVICEDESC desc_HEL = m_desc; + + if (m_deviceGUID == IID_IDirect3DRGBDevice) { + desc_HAL.dwFlags = 0; + desc_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + desc_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL; + desc_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL; + desc_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + desc_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + } else if (m_deviceGUID == IID_IDirect3DHALDevice) { + desc_HEL.dcmColorModel = 0; + desc_HEL.dwDevCaps &= ~D3DDEVCAPS_HWTRANSFORMANDLIGHT + & ~D3DDEVCAPS_DRAWPRIMITIVES2 + & ~D3DDEVCAPS_DRAWPRIMITIVES2EX; + } else { + Logger::warn("D3D6Device::GetCaps: Unhandled device type"); + } + + memcpy(hal_desc, &desc_HAL, hal_desc->dwSize); + memcpy(hel_desc, &desc_HEL, hel_desc->dwSize); + + return D3D_OK; + } + + // Docs state: "The IDirect3DDevice3::GetStats method is obsolete, + // and not implemented in the IDirect3DDevice3 interface." + HRESULT STDMETHODCALLTYPE D3D6Device::GetStats(D3DSTATS *stats) { + Logger::debug(">>> D3D6Device::GetStats"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::AddViewport(IDirect3DViewport3 *viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::AddViewport"); + + if (unlikely(viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D6Viewport* d3d6Viewport = static_cast(viewport); + HRESULT hr = m_proxy->AddViewport(d3d6Viewport->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + AddViewportInternal(viewport); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::DeleteViewport(IDirect3DViewport3 *viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::DeleteViewport"); + + if (unlikely(viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D6Viewport* d3d6Viewport = static_cast(viewport); + HRESULT hr = m_proxy->DeleteViewport(d3d6Viewport->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + DeleteViewportInternal(viewport); + + // Clear the current viewport if it is deleted from the device + if (m_currentViewport.ptr() == d3d6Viewport) + m_currentViewport = nullptr; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::NextViewport(IDirect3DViewport3 *lpDirect3DViewport, IDirect3DViewport3 **lplpAnotherViewport, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::NextViewport"); + + if (unlikely(lplpAnotherViewport == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpAnotherViewport); + + if (flags & D3DNEXT_HEAD) { + if (likely(m_viewports.size() > 0)) + *lplpAnotherViewport = m_viewports.front().ref(); + } else if (flags & D3DNEXT_NEXT) { + if (unlikely(lpDirect3DViewport == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(m_viewports.size() > 0)) + Logger::warn("D3D6Device::NextViewport: Unimplemented D3DNEXT_NEXT flag"); + } else if (flags & D3DNEXT_TAIL) { + if (likely(m_viewports.size() > 0)) + *lplpAnotherViewport = m_viewports.back().ref(); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::EnumTextureFormats(LPD3DENUMPIXELFORMATSCALLBACK cb, void *ctx) { + Logger::debug(">>> D3D6Device::EnumTextureFormats"); + + if (unlikely(cb == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // Note: The list of formats exposed in D3D6 is restricted to the below + + DDPIXELFORMAT textureFormat = GetTextureFormat(d3d9::D3DFMT_X1R5G5B5); + HRESULT hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_A1R5G5B5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + // D3DFMT_X4R4G4B4 is not supported by D3D6 + textureFormat = GetTextureFormat(d3d9::D3DFMT_A4R4G4B4); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_R5G6B5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_X8R8G8B8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_A8R8G8B8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + // Not supported in D3D9, but some games need + // it to be advertised (for offscreen plain surfaces?) + if (unlikely(d3dOptions->supportR3G3B2)) { + textureFormat = GetTextureFormat(d3d9::D3DFMT_R3G3B2); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + } + + // Not supported in D3D9, but some games may use it + /*textureFormat = GetTextureFormat(d3d9::D3DFMT_P8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK;*/ + + textureFormat = GetTextureFormat(d3d9::D3DFMT_V8U8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_L6V5U5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_X8L8V8U8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT1); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT2); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT3); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT4); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::BeginScene() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::BeginScene"); + + RefreshLastUsedDevice(); + + if (unlikely(m_inScene)) + return D3DERR_SCENE_IN_SCENE; + + HRESULT hr = m_d3d9->BeginScene(); + + if (likely(SUCCEEDED(hr))) + m_inScene = true; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::EndScene() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::EndScene"); + + RefreshLastUsedDevice(); + + if (unlikely(!m_inScene)) + return D3DERR_SCENE_NOT_IN_SCENE; + + HRESULT hr = m_d3d9->EndScene(); + + if (likely(SUCCEEDED(hr))) { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (d3dOptions->forceProxiedPresent) { + // If we have drawn anything, we need to make sure we blit back + // the results onto the D3D6 render target before we flip it + if (m_commonIntf->HasDrawn()) + BlitToDDrawSurface(m_rt->GetProxied(), m_rt->GetD3D9()); + + m_rt->GetProxied()->Flip(static_cast(m_commonIntf->GetFlipRTSurface()), + m_commonIntf->GetFlipRTFlags()); + + if (likely(d3dOptions->backBufferGuard != D3DBackBufferGuard::Strict)) + m_commonIntf->ResetDrawTracking(); + } + + m_inScene = false; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetDirect3D(IDirect3D3 **d3d) { + Logger::debug(">>> D3D6Device::GetDirect3D"); + + if (unlikely(d3d == nullptr)) + return DDERR_INVALIDPARAMS; + + *d3d = ref(m_parent); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::SetCurrentViewport(IDirect3DViewport3 *viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::SetCurrentViewport"); + + if (unlikely(viewport == nullptr)) + return DDERR_INVALIDPARAMS; + + Com d3d6Viewport = static_cast(viewport); + HRESULT hr = m_proxy->SetCurrentViewport(d3d6Viewport->GetProxied()); + if (unlikely(FAILED(hr))) { + Logger::debug("D3D6Device::SetCurrentViewport: Failed to set proxied viewport"); + return hr; + } + + if (unlikely(m_currentViewport == d3d6Viewport)) + return D3D_OK; + + if (likely(m_currentViewport != nullptr)) + m_currentViewport->GetCommonViewport()->SetIsCurrentViewport(false); + + m_currentViewport = d3d6Viewport.ptr(); + + m_currentViewport->GetCommonViewport()->SetIsCurrentViewport(true); + m_currentViewport->ApplyViewport(); + m_currentViewport->ApplyAndActivateLights(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetCurrentViewport(IDirect3DViewport3 **viewport) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::GetCurrentViewport"); + + if (unlikely(viewport == nullptr)) + return D3DERR_NOCURRENTVIEWPORT; + + InitReturnPtr(viewport); + + if (unlikely(m_currentViewport == nullptr)) + return D3DERR_NOCURRENTVIEWPORT; + + *viewport = m_currentViewport.ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::SetRenderTarget(IDirectDrawSurface4 *surface, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::SetRenderTarget"); + + if (unlikely(surface == nullptr)) { + Logger::err("D3D6Device::SetRenderTarget: NULL render target"); + return DDERR_INVALIDPARAMS; + } + + if (unlikely(!m_commonIntf->IsWrappedSurface(surface))) { + Logger::err("D3D6Device::SetRenderTarget: Received an unwrapped RT"); + return DDERR_GENERIC; + } + + DDraw4Surface* rt6 = static_cast(surface); + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->forceProxiedPresent)) { + HRESULT hrRT = m_proxy->SetRenderTarget(rt6->GetProxied(), flags); + if (unlikely(FAILED(hrRT))) { + Logger::warn("D3D6Device::SetRenderTarget: Failed to set RT"); + return hrRT; + } + } else { + // Needed to ensure proxied Z/Stencil viewport clears will work + HRESULT hrRT = m_proxy->SetRenderTarget(rt6->GetProxied(), flags); + if (unlikely(FAILED(hrRT))) + Logger::debug("D3D6Device::SetRenderTarget: Failed to set RT"); + } + + HRESULT hr = rt6->GetCommonSurface()->ValidateRTUsage(); + if (unlikely(FAILED(hr))) + return hr; + + hr = rt6->InitializeD3D9RenderTarget(); + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Device::SetRenderTarget: Failed to initialize D3D9 RT"); + return hr; + } + + hr = m_d3d9->SetRenderTarget(0, rt6->GetD3D9()); + + if (likely(SUCCEEDED(hr))) { + Logger::debug("D3D6Device::SetRenderTarget: Set a new D3D9 RT"); + + m_rt = rt6; + m_ds = m_rt->GetAttachedDepthStencil(); + + HRESULT hrDS; + + if (m_ds != nullptr) { + Logger::debug("D3D6Device::SetRenderTarget: Found an attached DS"); + + hrDS = m_ds->InitializeD3D9DepthStencil(); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D6Device::SetRenderTarget: Failed to initialize/upload D3D9 DS"); + return hrDS; + } + + hrDS = m_d3d9->SetDepthStencilSurface(m_ds->GetD3D9()); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D6Device::SetRenderTarget: Failed to set D3D9 DS"); + return hrDS; + } + + Logger::debug("D3D6Device::SetRenderTarget: Set a new D3D9 DS"); + } else { + Logger::debug("D3D6Device::SetRenderTarget: RT has no depth stencil attached"); + + hrDS = m_d3d9->SetDepthStencilSurface(nullptr); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D6Device::SetRenderTarget: Failed to clear the D3D9 DS"); + return hrDS; + } + + Logger::debug("D3D6Device::SetRenderTarget: Cleared the D3D9 DS"); + } + } else { + Logger::err("D3D6Device::SetRenderTarget: Failed to set D3D9 RT"); + return hr; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetRenderTarget(IDirectDrawSurface4 **surface) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::GetRenderTarget"); + + if (unlikely(surface == nullptr)) + return DDERR_INVALIDPARAMS; + + *surface = m_rt.ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::Begin(D3DPRIMITIVETYPE d3dptPrimitiveType, DWORD dwVertexTypeDesc, DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::Begin"); + + // All FVF combinations are technically supported, + // but I doubt that is the case in practice + if (dwVertexTypeDesc != D3DFVF_VERTEX && + dwVertexTypeDesc != D3DFVF_LVERTEX && + dwVertexTypeDesc != D3DFVF_TLVERTEX) { + Logger::warn("D3D6Device::Begin: Unsupported FVF format"); + return DDERR_INVALIDPARAMS; + } + + m_vertexStreamInfo.d3dpt = d3dptPrimitiveType; + m_vertexStreamInfo.d3dvt = ConvertFVFType(dwVertexTypeDesc); + m_vertexStreamInfo.dwFlags = dwFlags; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::BeginIndexed(D3DPRIMITIVETYPE primitive_type, DWORD fvf, void *vertices, DWORD vertex_count, DWORD flags) { + Logger::warn("!!! D3D6Device::BeginIndexed: Stub"); + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::Vertex(void *vertex) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::Vertex"); + + if (unlikely(vertex == nullptr)) + return DDERR_INVALIDPARAMS; + + if (m_vertexStreamInfo.d3dvt == D3DVT_VERTEX) { + m_vertexStream.push_back(*reinterpret_cast(vertex)); + } else if (m_vertexStreamInfo.d3dvt == D3DVT_LVERTEX) { + m_lvertexStream.push_back(*reinterpret_cast(vertex)); + } else if (m_vertexStreamInfo.d3dvt == D3DVT_TLVERTEX) { + m_tlvertexStream.push_back(*reinterpret_cast(vertex)); + } else { + Logger::warn(">>> D3D6Device::Vertex: Invalid vertex type"); + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::Index(WORD wVertexIndex) { + Logger::warn("!!! D3D6Device::Index: Stub"); + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::End(DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::End"); + + HRESULT hr; + if (m_vertexStreamInfo.d3dvt == D3DVT_VERTEX) { + hr = DrawPrimitive(m_vertexStreamInfo.d3dpt, ConvertVertexType(m_vertexStreamInfo.d3dvt), m_vertexStream.data(), + m_vertexStream.size(), m_vertexStreamInfo.dwFlags); + m_vertexStream.clear(); + } else if (m_vertexStreamInfo.d3dvt == D3DVT_LVERTEX) { + hr = DrawPrimitive(m_vertexStreamInfo.d3dpt, ConvertVertexType(m_vertexStreamInfo.d3dvt), m_lvertexStream.data(), + m_lvertexStream.size(), m_vertexStreamInfo.dwFlags); + m_lvertexStream.clear(); + } else if (m_vertexStreamInfo.d3dvt == D3DVT_TLVERTEX) { + hr = DrawPrimitive(m_vertexStreamInfo.d3dpt, ConvertVertexType(m_vertexStreamInfo.d3dvt), m_tlvertexStream.data(), + m_tlvertexStream.size(), m_vertexStreamInfo.dwFlags); + m_tlvertexStream.clear(); + } else { + Logger::warn(">>> D3D6Device::End: Invalid vertex type"); + return DDERR_INVALIDPARAMS; + } + + if (unlikely(FAILED(hr))) + Logger::warn(">>> D3D6Device::End: Failed call to DrawPrimitive"); + + m_vertexStreamInfo = { }; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetRenderState(D3DRENDERSTATETYPE dwRenderStateType, LPDWORD lpdwRenderState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(str::format(">>> D3D6Device::GetRenderState: ", dwRenderStateType)); + + if (unlikely(lpdwRenderState == nullptr)) + return DDERR_INVALIDPARAMS; + + // As opposed to D3D7, D3D6 does not error out on + // unknown or invalid render states. + if (unlikely(!IsValidD3D6RenderStateType(dwRenderStateType))) { + Logger::debug(str::format("D3D6Device::GetRenderState: Invalid render state ", dwRenderStateType)); + *lpdwRenderState = 0; + return D3D_OK; + } + + d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(dwRenderStateType); + + switch (dwRenderStateType) { + // Most render states translate 1:1 to D3D9 + default: + break; + + // "Texture handle for use when rendering with the IDirect3DDevice2 or earlier interfaces." + case D3DRENDERSTATE_TEXTUREHANDLE: + *lpdwRenderState = 0; + return D3D_OK; + + case D3DRENDERSTATE_ANTIALIAS: + *lpdwRenderState = m_antialias; + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESS: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, lpdwRenderState); + return D3D_OK; + + // Always enabled on later APIs, default TRUE in D3D6 + case D3DRENDERSTATE_TEXTUREPERSPECTIVE: + *lpdwRenderState = TRUE; + return D3D_OK; + + // Not implemented in DXVK, but retrieve it as it were + case D3DRENDERSTATE_WRAPU: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + *lpdwRenderState = value9 & D3DWRAP_U; + return D3D_OK; + } + + // Not implemented in DXVK, but retrieve it as it were + case D3DRENDERSTATE_WRAPV: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + *lpdwRenderState = value9 & D3DWRAP_V; + return D3D_OK; + } + + case D3DRENDERSTATE_LINEPATTERN: + *lpdwRenderState = bit::cast(m_linePattern); + return D3D_OK; + + case D3DRENDERSTATE_MONOENABLE: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_ROP2: + *lpdwRenderState = R2_COPYPEN; + return D3D_OK; + + case D3DRENDERSTATE_PLANEMASK: + *lpdwRenderState = 0; + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREMAG: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MAGFILTER, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREMIN: { + DWORD minFilter = 0; + DWORD mipFilter = 0; + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MINFILTER, &minFilter); + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, &mipFilter); + *lpdwRenderState = DecodeTextureMinValues(minFilter, mipFilter); + return D3D_OK; + } + + case D3DRENDERSTATE_TEXTUREMAPBLEND: + *lpdwRenderState = m_textureMapBlend; + return D3D_OK; + + // Not supported by D3D6 + case D3DRENDERSTATE_ZVISIBLE: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_SUBPIXEL: + case D3DRENDERSTATE_SUBPIXELX: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_STIPPLEDALPHA: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_STIPPLEENABLE: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_EDGEANTIALIAS: + State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE; + break; + + case D3DRENDERSTATE_COLORKEYENABLE: + *lpdwRenderState = m_colorKeyEnabled; + return D3D_OK; + + case D3DRENDERSTATE_BORDERCOLOR: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_BORDERCOLOR, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESSU: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESSV: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_ADDRESSV, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_MIPMAPLODBIAS: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MIPMAPLODBIAS, lpdwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_ZBIAS: { + DWORD bias = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_DEPTHBIAS, &bias); + *lpdwRenderState = static_cast(bit::cast(bias) * ddrawCaps::ZBIAS_SCALE_INV); + return D3D_OK; + } + + case D3DRENDERSTATE_ANISOTROPY: + m_d3d9->GetSamplerState(0, d3d9::D3DSAMP_MAXANISOTROPY, lpdwRenderState); + return D3D_OK; + + // "Batched primitives are implicitly flushed when rendering with the + // IDirect3DDevice3 interface, as well as when rendering with execute buffers." + case D3DRENDERSTATE_FLUSHBATCH: + *lpdwRenderState = TRUE; + return D3D_OK; + + case D3DRENDERSTATE_TRANSLUCENTSORTINDEPENDENT: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_STIPPLEPATTERN00: + case D3DRENDERSTATE_STIPPLEPATTERN01: + case D3DRENDERSTATE_STIPPLEPATTERN02: + case D3DRENDERSTATE_STIPPLEPATTERN03: + case D3DRENDERSTATE_STIPPLEPATTERN04: + case D3DRENDERSTATE_STIPPLEPATTERN05: + case D3DRENDERSTATE_STIPPLEPATTERN06: + case D3DRENDERSTATE_STIPPLEPATTERN07: + case D3DRENDERSTATE_STIPPLEPATTERN08: + case D3DRENDERSTATE_STIPPLEPATTERN09: + case D3DRENDERSTATE_STIPPLEPATTERN10: + case D3DRENDERSTATE_STIPPLEPATTERN11: + case D3DRENDERSTATE_STIPPLEPATTERN12: + case D3DRENDERSTATE_STIPPLEPATTERN13: + case D3DRENDERSTATE_STIPPLEPATTERN14: + case D3DRENDERSTATE_STIPPLEPATTERN15: + case D3DRENDERSTATE_STIPPLEPATTERN16: + case D3DRENDERSTATE_STIPPLEPATTERN17: + case D3DRENDERSTATE_STIPPLEPATTERN18: + case D3DRENDERSTATE_STIPPLEPATTERN19: + case D3DRENDERSTATE_STIPPLEPATTERN20: + case D3DRENDERSTATE_STIPPLEPATTERN21: + case D3DRENDERSTATE_STIPPLEPATTERN22: + case D3DRENDERSTATE_STIPPLEPATTERN23: + case D3DRENDERSTATE_STIPPLEPATTERN24: + case D3DRENDERSTATE_STIPPLEPATTERN25: + case D3DRENDERSTATE_STIPPLEPATTERN26: + case D3DRENDERSTATE_STIPPLEPATTERN27: + case D3DRENDERSTATE_STIPPLEPATTERN28: + case D3DRENDERSTATE_STIPPLEPATTERN29: + case D3DRENDERSTATE_STIPPLEPATTERN30: + case D3DRENDERSTATE_STIPPLEPATTERN31: + *lpdwRenderState = 0; + return D3D_OK; + } + + // This call will never fail + return m_d3d9->GetRenderState(State9, lpdwRenderState); + } + + HRESULT STDMETHODCALLTYPE D3D6Device::SetRenderState(D3DRENDERSTATETYPE dwRenderStateType, DWORD dwRenderState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(str::format(">>> D3D6Device::SetRenderState: ", dwRenderStateType)); + + // As opposed to D3D7, D3D6 does not error out on + // unknown or invalid render states. + if (unlikely(!IsValidD3D6RenderStateType(dwRenderStateType))) { + Logger::debug(str::format("D3D6Device::SetRenderState: Invalid render state ", dwRenderStateType)); + return D3D_OK; + } + + d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(dwRenderStateType); + + switch (dwRenderStateType) { + // Most render states translate 1:1 to D3D9 + default: + break; + + // "Texture handle for use when rendering with the IDirect3DDevice2 or earlier interfaces." + case D3DRENDERSTATE_TEXTUREHANDLE: + return D3D_OK; + + case D3DRENDERSTATE_ANTIALIAS: { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (likely(d3dOptions->emulateFSAA == FSAAEmulation::Disabled)) { + if (unlikely(dwRenderState == D3DANTIALIAS_SORTDEPENDENT + || dwRenderState == D3DANTIALIAS_SORTINDEPENDENT)) + Logger::warn("D3D6Device::SetRenderState: Device does not expose FSAA emulation"); + return D3D_OK; + } + + State9 = d3d9::D3DRS_MULTISAMPLEANTIALIAS; + m_antialias = dwRenderState; + dwRenderState = m_antialias == D3DANTIALIAS_SORTDEPENDENT + || m_antialias == D3DANTIALIAS_SORTINDEPENDENT + || d3dOptions->emulateFSAA == FSAAEmulation::Forced ? TRUE : FALSE; + break; + } + + case D3DRENDERSTATE_TEXTUREADDRESS: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, dwRenderState); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSV, dwRenderState); + return D3D_OK; + + // Always enabled on later APIs, default TRUE in D3D6 + case D3DRENDERSTATE_TEXTUREPERSPECTIVE: + return D3D_OK; + + // Not implemented in DXVK, but forward it anyway + case D3DRENDERSTATE_WRAPU: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + if (dwRenderState == TRUE) { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & D3DWRAP_U); + } else { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & ~D3DWRAP_U); + } + return D3D_OK; + } + + // Not implemented in DXVK, but forward it anyway + case D3DRENDERSTATE_WRAPV: { + DWORD value9 = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_WRAP0, &value9); + if (dwRenderState == TRUE) { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & D3DWRAP_V); + } else { + m_d3d9->SetRenderState(d3d9::D3DRS_WRAP0, value9 & ~D3DWRAP_V); + } + return D3D_OK; + } + + // TODO: Implement D3DRS_LINEPATTERN - vkCmdSetLineRasterizationModeEXT + // and advertise support with D3DPRASTERCAPS_PAT once that is done + case D3DRENDERSTATE_LINEPATTERN: + static bool s_linePatternErrorShown; + + if (!std::exchange(s_linePatternErrorShown, true)) + Logger::warn("D3D6Device::SetRenderState: Unimplemented render state D3DRS_LINEPATTERN"); + + m_linePattern = bit::cast(dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_MONOENABLE: + static bool s_monoEnableErrorShown; + + if (dwRenderState && !std::exchange(s_monoEnableErrorShown, true)) + Logger::warn("D3D6Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_MONOENABLE"); + + return D3D_OK; + + case D3DRENDERSTATE_ROP2: + static bool s_ROP2ErrorShown; + + if (!std::exchange(s_ROP2ErrorShown, true)) + Logger::warn("D3D6Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_ROP2"); + + return D3D_OK; + + // Docs state: "This render state is not supported by the software + // rasterizers, and is often ignored by hardware drivers." + case D3DRENDERSTATE_PLANEMASK: + return D3D_OK; + + // Docs: "[...] only the first two (D3DFILTER_NEAREST and + // D3DFILTER_LINEAR) are valid with D3DRENDERSTATE_TEXTUREMAG." + case D3DRENDERSTATE_TEXTUREMAG: { + switch (dwRenderState) { + case D3DFILTER_NEAREST: + case D3DFILTER_LINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MAGFILTER, dwRenderState); + break; + default: + break; + } + return D3D_OK; + } + + case D3DRENDERSTATE_TEXTUREMIN: { + switch (dwRenderState) { + case D3DFILTER_NEAREST: + case D3DFILTER_LINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, dwRenderState); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_NONE); + break; + // "The closest mipmap level is chosen and a point filter is applied." + case D3DFILTER_MIPNEAREST: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_POINT); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_POINT); + break; + // "The closest mipmap level is chosen and a bilinear filter is applied within it." + case D3DFILTER_MIPLINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_LINEAR); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_POINT); + break; + // "The two closest mipmap levels are chosen and then a linear + // blend is used between point filtered samples of each level." + case D3DFILTER_LINEARMIPNEAREST: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_POINT); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_LINEAR); + break; + // "The two closest mipmap levels are chosen and then combined using a bilinear filter." + case D3DFILTER_LINEARMIPLINEAR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MINFILTER, d3d9::D3DTEXF_LINEAR); + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPFILTER, d3d9::D3DTEXF_LINEAR); + break; + default: + break; + } + return D3D_OK; + } + + case D3DRENDERSTATE_TEXTUREMAPBLEND: + m_textureMapBlend = dwRenderState; + + switch (dwRenderState) { + // "In this mode, the RGB and alpha values of the texture replace + // the colors that would have been used with no texturing." + case D3DTBLEND_DECAL: + case D3DTBLEND_COPY: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_CURRENT); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_CURRENT); + break; + // "In this mode, the RGB values of the texture are multiplied with the RGB values + // that would have been used with no texturing. Any alpha values in the texture + // replace the alpha values in the colors that would have been used with no texturing; + // if the texture does not contain an alpha component, alpha values at the vertices + // in the source are interpolated between vertices." + case D3DTBLEND_MODULATE: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "In this mode, the RGB and alpha values of the texture are blended with the colors + // that would have been used with no texturing, according to the following formulas [...]" + case D3DTBLEND_DECALALPHA: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_BLENDTEXTUREALPHA); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "In this mode, the RGB values of the texture are multiplied with the RGB values that + // would have been used with no texturing, and the alpha values of the texture + // are multiplied with the alpha values that would have been used with no texturing." + case D3DTBLEND_MODULATEALPHA: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_MODULATE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // "Add the Gouraud interpolants to the texture lookup with saturation semantics + // (that is, if the color value overflows it is set to the maximum possible value)." + case D3DTBLEND_ADD: + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLOROP, D3DTOP_ADD); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + break; + // Unsupported + default: + case D3DTBLEND_DECALMASK: + case D3DTBLEND_MODULATEMASK: + break; + } + + return D3D_OK; + + // Not supported by D3D6 + case D3DRENDERSTATE_ZVISIBLE: + return D3D_OK; + + // Docs state: "Most hardware either doesn't support it (always off) or + // always supports it (always on).", and "All hardware should be subpixel correct. + // Some software rasterizers are not subpixel correct because of the performance loss." + case D3DRENDERSTATE_SUBPIXEL: + case D3DRENDERSTATE_SUBPIXELX: + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEDALPHA: + static bool s_stippledAlphaErrorShown; + + if (dwRenderState && !std::exchange(s_stippledAlphaErrorShown, true)) + Logger::warn("D3D6Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_STIPPLEDALPHA"); + + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEENABLE: + static bool s_stippleEnableErrorShown; + + if (dwRenderState && !std::exchange(s_stippleEnableErrorShown, true)) + Logger::warn("D3D6Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_STIPPLEENABLE"); + + return D3D_OK; + + case D3DRENDERSTATE_EDGEANTIALIAS: + State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE; + break; + + case D3DRENDERSTATE_COLORKEYENABLE: { + m_colorKeyEnabled = dwRenderState; + + const bool validColorKey = m_textures[0] != nullptr ? m_textures[0]->GetParent()->GetCommonSurface()->HasValidColorKey() : false; + m_bridge->SetColorKeyState(m_colorKeyEnabled && validColorKey); + if (m_colorKeyEnabled && validColorKey) { + DDCOLORKEY normalizedColorKey = m_textures[0]->GetParent()->GetCommonSurface()->GetColorKeyNormalized(); + m_bridge->SetColorKey(normalizedColorKey.dwColorSpaceLowValue, + normalizedColorKey.dwColorSpaceHighValue); + } + + return D3D_OK; + } + + case D3DRENDERSTATE_BORDERCOLOR: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_BORDERCOLOR, dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESSU: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSU, dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_TEXTUREADDRESSV: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_ADDRESSV, dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_MIPMAPLODBIAS: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MIPMAPLODBIAS, dwRenderState); + return D3D_OK; + + case D3DRENDERSTATE_ZBIAS: + State9 = d3d9::D3DRS_DEPTHBIAS; + dwRenderState = bit::cast(static_cast(dwRenderState) * ddrawCaps::ZBIAS_SCALE); + break; + + case D3DRENDERSTATE_ANISOTROPY: + m_d3d9->SetSamplerState(0, d3d9::D3DSAMP_MAXANISOTROPY, dwRenderState); + return D3D_OK; + + // "Batched primitives are implicitly flushed when rendering with the + // IDirect3DDevice3 interface, as well as when rendering with execute buffers." + case D3DRENDERSTATE_FLUSHBATCH: + return D3D_OK; + + // Unclear if this ever did much as far as the runtime was concered. + // Most likely it was passed to the driver, though I don't expect + // it was ever implemented, as it's a D3D6-only render state. + case D3DRENDERSTATE_TRANSLUCENTSORTINDEPENDENT: + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEPATTERN00: + case D3DRENDERSTATE_STIPPLEPATTERN01: + case D3DRENDERSTATE_STIPPLEPATTERN02: + case D3DRENDERSTATE_STIPPLEPATTERN03: + case D3DRENDERSTATE_STIPPLEPATTERN04: + case D3DRENDERSTATE_STIPPLEPATTERN05: + case D3DRENDERSTATE_STIPPLEPATTERN06: + case D3DRENDERSTATE_STIPPLEPATTERN07: + case D3DRENDERSTATE_STIPPLEPATTERN08: + case D3DRENDERSTATE_STIPPLEPATTERN09: + case D3DRENDERSTATE_STIPPLEPATTERN10: + case D3DRENDERSTATE_STIPPLEPATTERN11: + case D3DRENDERSTATE_STIPPLEPATTERN12: + case D3DRENDERSTATE_STIPPLEPATTERN13: + case D3DRENDERSTATE_STIPPLEPATTERN14: + case D3DRENDERSTATE_STIPPLEPATTERN15: + case D3DRENDERSTATE_STIPPLEPATTERN16: + case D3DRENDERSTATE_STIPPLEPATTERN17: + case D3DRENDERSTATE_STIPPLEPATTERN18: + case D3DRENDERSTATE_STIPPLEPATTERN19: + case D3DRENDERSTATE_STIPPLEPATTERN20: + case D3DRENDERSTATE_STIPPLEPATTERN21: + case D3DRENDERSTATE_STIPPLEPATTERN22: + case D3DRENDERSTATE_STIPPLEPATTERN23: + case D3DRENDERSTATE_STIPPLEPATTERN24: + case D3DRENDERSTATE_STIPPLEPATTERN25: + case D3DRENDERSTATE_STIPPLEPATTERN26: + case D3DRENDERSTATE_STIPPLEPATTERN27: + case D3DRENDERSTATE_STIPPLEPATTERN28: + case D3DRENDERSTATE_STIPPLEPATTERN29: + case D3DRENDERSTATE_STIPPLEPATTERN30: + case D3DRENDERSTATE_STIPPLEPATTERN31: + static bool s_stipplePatternErrorShown; + + if (!std::exchange(s_stipplePatternErrorShown, true)) + Logger::warn("D3D6Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_STIPPLEPATTERN"); + + return D3D_OK; + } + + // This call will never fail + return m_d3d9->SetRenderState(State9, dwRenderState); + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetLightState(D3DLIGHTSTATETYPE dwLightStateType, LPDWORD lpdwLightState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::GetLightState"); + + switch (dwLightStateType) { + case D3DLIGHTSTATE_MATERIAL: + *lpdwLightState = m_materialHandle; + break; + case D3DLIGHTSTATE_AMBIENT: + m_d3d9->GetRenderState(d3d9::D3DRS_AMBIENT, lpdwLightState); + break; + case D3DLIGHTSTATE_COLORMODEL: + *lpdwLightState = D3DCOLOR_RGB; + break; + case D3DLIGHTSTATE_FOGMODE: + m_d3d9->GetRenderState(d3d9::D3DRS_FOGVERTEXMODE, lpdwLightState); + break; + case D3DLIGHTSTATE_FOGSTART: + m_d3d9->GetRenderState(d3d9::D3DRS_FOGSTART, lpdwLightState); + break; + case D3DLIGHTSTATE_FOGEND: + m_d3d9->GetRenderState(d3d9::D3DRS_FOGEND, lpdwLightState); + break; + case D3DLIGHTSTATE_FOGDENSITY: + m_d3d9->GetRenderState(d3d9::D3DRS_FOGDENSITY, lpdwLightState); + break; + case D3DLIGHTSTATE_COLORVERTEX: + m_d3d9->GetRenderState(d3d9::D3DRS_COLORVERTEX, lpdwLightState); + break; + default: + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::SetLightState(D3DLIGHTSTATETYPE dwLightStateType, DWORD dwLightState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::SetLightState"); + + switch (dwLightStateType) { + case D3DLIGHTSTATE_MATERIAL: { + if (unlikely(!dwLightState)) { + m_materialHandle = dwLightState; + return D3D_OK; + } + + d3d9::D3DMATERIAL9* material9 = m_parent->GetCommonD3DInterface()->GetD3D9MaterialFromHandle(dwLightState); + if (unlikely(material9 == nullptr)) + return DDERR_INVALIDPARAMS; + + m_materialHandle = dwLightState; + Logger::debug(str::format("D3D6Device::SetLightState: Applying material nr. ", dwLightState, " to D3D9")); + m_d3d9->SetMaterial(material9); + + break; + } + case D3DLIGHTSTATE_AMBIENT: + m_d3d9->SetRenderState(d3d9::D3DRS_AMBIENT, dwLightState); + break; + case D3DLIGHTSTATE_COLORMODEL: + if (unlikely(dwLightState != D3DCOLOR_RGB)) + Logger::warn("D3D6Device::SetLightState: Unsupported D3DLIGHTSTATE_COLORMODEL"); + break; + case D3DLIGHTSTATE_FOGMODE: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGVERTEXMODE, dwLightState); + break; + case D3DLIGHTSTATE_FOGSTART: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGSTART, dwLightState); + break; + case D3DLIGHTSTATE_FOGEND: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGEND, dwLightState); + break; + case D3DLIGHTSTATE_FOGDENSITY: + m_d3d9->SetRenderState(d3d9::D3DRS_FOGDENSITY, dwLightState); + break; + case D3DLIGHTSTATE_COLORVERTEX: + m_d3d9->SetRenderState(d3d9::D3DRS_COLORVERTEX, dwLightState); + break; + default: + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::SetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D6Device::SetTransform"); + + // Need to also proxy for viewport TransformVertices calls to work + HRESULT hr = m_proxy->SetTransform(state, matrix); + if (unlikely(FAILED(hr))) + return hr; + + return m_d3d9->SetTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D6Device::GetTransform"); + return m_d3d9->GetTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D6Device::MultiplyTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D6Device::MultiplyTransform"); + + // Need to also proxy for viewport TransformVertices calls to work + HRESULT hr = m_proxy->MultiplyTransform(state, matrix); + if (unlikely(FAILED(hr))) + return hr; + + return m_d3d9->MultiplyTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D6Device::DrawPrimitive(D3DPRIMITIVETYPE primitive_type, DWORD vertex_type, void *vertices, DWORD vertex_count, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::DrawPrimitive"); + + RefreshLastUsedDevice(); + + if (unlikely(!vertex_count)) + return D3D_OK; + + if (unlikely(vertices == nullptr)) + return DDERR_INVALIDPARAMS; + + HandlePreDrawFlags(flags, vertex_type); + HandlePreDrawLegacyProjection(flags); + + m_d3d9->SetFVF(vertex_type); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(primitive_type), + GetPrimitiveCount(primitive_type, vertex_count), + vertices, + GetFVFSize(vertex_type)); + + HandlePostDrawLegacyProjection(); + HandlePostDrawFlags(flags, vertex_type); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Device::DrawPrimitive: Failed D3D9 call to DrawPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::DrawIndexedPrimitive(D3DPRIMITIVETYPE primitive_type, DWORD fvf, void *vertices, DWORD vertex_count, WORD *indices, DWORD index_count, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::DrawIndexedPrimitive"); + + RefreshLastUsedDevice(); + + if (unlikely(!vertex_count || !index_count)) + return D3D_OK; + + if (unlikely(vertices == nullptr || indices == nullptr)) + return DDERR_INVALIDPARAMS; + + HandlePreDrawFlags(flags, fvf); + HandlePreDrawLegacyProjection(flags); + + m_d3d9->SetFVF(fvf); + HRESULT hr = m_d3d9->DrawIndexedPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(primitive_type), + 0, + vertex_count, + GetPrimitiveCount(primitive_type, index_count), + indices, + d3d9::D3DFMT_INDEX16, + vertices, + GetFVFSize(fvf)); + + HandlePostDrawLegacyProjection(); + HandlePostDrawFlags(flags, fvf); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Device::DrawIndexedPrimitive: Failed D3D9 call to DrawIndexedPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::SetClipStatus(D3DCLIPSTATUS *clip_status) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::SetClipStatus"); + + if (unlikely(clip_status == nullptr)) + return DDERR_INVALIDPARAMS; + + m_clipStatus = *clip_status; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetClipStatus(D3DCLIPSTATUS *clip_status) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::GetClipStatus"); + + if (unlikely(clip_status == nullptr)) + return DDERR_INVALIDPARAMS; + + *clip_status = m_clipStatus; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::DrawPrimitiveStrided(D3DPRIMITIVETYPE primitive_type, DWORD fvf, D3DDRAWPRIMITIVESTRIDEDDATA *strided_data, DWORD vertex_count, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::DrawPrimitiveStrided"); + + RefreshLastUsedDevice(); + + if (unlikely(!vertex_count)) + return D3D_OK; + + if (unlikely(strided_data == nullptr)) + return DDERR_INVALIDPARAMS; + + // Transform strided vertex data to a standard vertex buffer stream + PackedVertexBuffer pvb = TransformStridedtoUP(fvf, strided_data, vertex_count); + + HandlePreDrawFlags(flags, fvf); + HandlePreDrawLegacyProjection(flags); + + m_d3d9->SetFVF(fvf); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(primitive_type), + GetPrimitiveCount(primitive_type, vertex_count), + pvb.vertexData.data(), + pvb.stride); + + HandlePostDrawLegacyProjection(); + HandlePostDrawFlags(flags, fvf); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Device::DrawPrimitiveStrided: Failed D3D9 call to DrawPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::DrawIndexedPrimitiveStrided(D3DPRIMITIVETYPE primitive_type, DWORD fvf, D3DDRAWPRIMITIVESTRIDEDDATA *strided_data, DWORD vertex_count, WORD *indices, DWORD index_count, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::DrawIndexedPrimitiveStrided"); + + RefreshLastUsedDevice(); + + if (unlikely(!vertex_count || !index_count)) + return D3D_OK; + + if (unlikely(strided_data == nullptr || indices == nullptr)) + return DDERR_INVALIDPARAMS; + + // Transform strided vertex data to a standard vertex buffer stream + PackedVertexBuffer pvb = TransformStridedtoUP(fvf, strided_data, vertex_count); + + HandlePreDrawFlags(flags, fvf); + HandlePreDrawLegacyProjection(flags); + + m_d3d9->SetFVF(fvf); + HRESULT hr = m_d3d9->DrawIndexedPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(primitive_type), + 0, + vertex_count, + GetPrimitiveCount(primitive_type, index_count), + indices, + d3d9::D3DFMT_INDEX16, + pvb.vertexData.data(), + pvb.stride); + + HandlePostDrawLegacyProjection(); + HandlePostDrawFlags(flags, fvf); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Device::DrawIndexedPrimitiveStrided: Failed D3D9 call to DrawIndexedPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::DrawPrimitiveVB(D3DPRIMITIVETYPE primitive_type, IDirect3DVertexBuffer *vb, DWORD start_vertex, DWORD vertex_count, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::DrawPrimitiveVB"); + + RefreshLastUsedDevice(); + + if (unlikely(!vertex_count)) + return D3D_OK; + + if (unlikely(vb == nullptr)) + return DDERR_INVALIDPARAMS; + + Com vb6 = static_cast(vb); + + if (unlikely(vb6->IsLocked())) { + Logger::err("D3D6Device::DrawPrimitiveVB: Buffer is locked"); + return D3DERR_VERTEXBUFFERLOCKED; + } + + HandlePreDrawFlags(flags, vb6->GetFVF()); + HandlePreDrawLegacyProjection(flags); + + m_d3d9->SetFVF(vb6->GetFVF()); + m_d3d9->SetStreamSource(0, vb6->GetD3D9(), 0, vb6->GetStride()); + HRESULT hr = m_d3d9->DrawPrimitive( + d3d9::D3DPRIMITIVETYPE(primitive_type), + start_vertex, + GetPrimitiveCount(primitive_type, vertex_count)); + + HandlePostDrawLegacyProjection(); + HandlePostDrawFlags(flags, vb6->GetFVF()); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Device::DrawPrimitiveVB: Failed D3D9 call to DrawPrimitive"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::DrawIndexedPrimitiveVB(D3DPRIMITIVETYPE primitive_type, IDirect3DVertexBuffer *vb, WORD *indices, DWORD index_count, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::DrawIndexedPrimitiveVB"); + + RefreshLastUsedDevice(); + + if (unlikely(!index_count)) + return D3D_OK; + + if (unlikely(vb == nullptr || indices == nullptr)) + return DDERR_INVALIDPARAMS; + + Com vb6 = static_cast(vb); + + if (unlikely(vb6->IsLocked())) { + Logger::err("D3D6Device::DrawIndexedPrimitiveVB: Buffer is locked"); + return D3DERR_VERTEXBUFFERLOCKED; + } + + uint8_t ibIndex = 0; + // Try to fit index buffer uploads into the smallest buffer size possible, + // out of the five available: XS, S, M, L and XL (XL being the theoretical max) + while (index_count > ddrawCaps::IndexCount[ibIndex]) { + ibIndex++; + if (unlikely(ibIndex > ddrawCaps::IndexBufferCount - 1)) { + Logger::err("D3D6Device::DrawIndexedPrimitiveVB: Exceeded size of largest index buffer"); + return DDERR_GENERIC; + } + } + + HandlePreDrawFlags(flags, vb6->GetFVF()); + HandlePreDrawLegacyProjection(flags); + + d3d9::IDirect3DIndexBuffer9* ib9 = m_ib9[ibIndex].ptr(); + + UploadIndices(ib9, indices, index_count); + m_ib9_uploads[ibIndex]++; + m_d3d9->SetIndices(ib9); + m_d3d9->SetFVF(vb6->GetFVF()); + m_d3d9->SetStreamSource(0, vb6->GetD3D9(), 0, vb6->GetStride()); + HRESULT hr = m_d3d9->DrawIndexedPrimitive( + d3d9::D3DPRIMITIVETYPE(primitive_type), + 0, + 0, + vb6->GetNumVertices(), + 0, + GetPrimitiveCount(primitive_type, index_count)); + + HandlePostDrawLegacyProjection(); + HandlePostDrawFlags(flags, vb6->GetFVF()); + + if(unlikely(FAILED(hr))) { + Logger::err("D3D6Device::DrawIndexedPrimitiveVB: Failed D3D9 call to DrawIndexedPrimitive"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::ComputeSphereVisibility(D3DVECTOR *lpCenters, D3DVALUE *lpRadii, DWORD dwNumSpheres, DWORD dwFlags, DWORD *lpdwReturnValues) { + Logger::debug(">>> D3D6Device::ComputeSphereVisibility"); + + if (unlikely(lpCenters == nullptr || lpRadii == nullptr || lpdwReturnValues == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(dwNumSpheres == 0)) + return D3D_OK; + + // Docs state: "The array need not be initialized, but it must be large enough to contain a DWORD for + // each sphere being tested. When the method returns, each element in the array contains a combination + // of flags that describe the visibility of that sphere within the current viewport for this device. + // If a sphere is completely visible, the corresponding entry in lpdwReturnValues is 0." + // Consider everything to be visible as a minimal implementation, which makes Space Empires V happy. + for (DWORD i = 0; i < dwNumSpheres; i++) + lpdwReturnValues[i] = 0; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetTexture(DWORD stage, IDirect3DTexture2 **texture) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::GetTexture"); + + if (unlikely(texture == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(stage >= ddrawCaps::TextureStageCount)) { + Logger::err(str::format("D3D6Device::GetTexture: Invalid texture stage: ", stage)); + return DDERR_INVALIDPARAMS; + } + + *texture = m_textures[stage].ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::SetTexture(DWORD stage, IDirect3DTexture2 *texture) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D6Device::SetTexture"); + + if (unlikely(stage >= ddrawCaps::TextureStageCount)) { + Logger::err(str::format("D3D6Device::SetTexture: Invalid texture stage: ", stage)); + return DDERR_INVALIDPARAMS; + } + + HRESULT hr; + + // Unbinding texture stages + if (texture == nullptr) { + Logger::debug("D3D6Device::SetTexture: Unbiding D3D9 texture"); + + hr = m_d3d9->SetTexture(stage, nullptr); + + if (likely(SUCCEEDED(hr))) { + if (m_textures[stage] != nullptr) { + Logger::debug("D3D6Device::SetTexture: Unbinding local texture"); + m_textures[stage] = nullptr; + + if (likely(stage == 0)) + m_bridge->SetColorKeyState(false); + } + } else { + Logger::err("D3D6Device::SetTexture: Failed to unbind D3D9 texture"); + } + + return hr; + } + + Logger::debug("D3D6Device::SetTexture: Binding D3D9 texture"); + + D3D6Texture* texture6 = static_cast(texture); + DDraw4Surface* surface6 = texture6->GetParent(); + + // Only upload textures if any sort of blit/lock operation + // has been performed on them since the last SetTexture call, + // or textures which have been used on a different device, and + // need their D3D9 object to be reinitialized at this point + if (surface6->GetCommonSurface()->HasDirtyMipMaps() || + unlikely(surface6->GetD3D9Device() != m_d3d9.ptr())) { + hr = surface6->InitializeOrUploadD3D9(); + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Device::SetTexture: Failed to initialize/upload D3D9 texture"); + return hr; + } + + surface6->GetCommonSurface()->UnDirtyMipMaps(); + } else { + Logger::debug("D3D6Device::SetTexture: Skipping upload of texture and mip maps"); + } + + // Only fast skip on D3D9 side, since we want to ensure + // color keying is applied properly even in the case + // of the same texture being set again (color key may change) + //if (unlikely(m_textures[stage] == texture6)) + //return D3D_OK; + + d3d9::IDirect3DTexture9* tex9 = surface6->GetD3D9Texture(); + + if (likely(tex9 != nullptr)) { + hr = m_d3d9->SetTexture(stage, tex9); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D6Device::SetTexture: Failed to bind D3D9 texture"); + return hr; + } + + if (likely(stage == 0)) { + // "Any alpha values in the texture replace the alpha values in the colors that would + // have been used with no texturing; if the texture does not contain an alpha component, + // alpha values at the vertices in the source are interpolated between vertices." + if (m_textureMapBlend == D3DTBLEND_MODULATE && !m_alphaOpSet) { + const DWORD textureOp = surface6->GetCommonSurface()->IsAlphaFormat() ? D3DTOP_SELECTARG1 : D3DTOP_MODULATE; + m_d3d9->SetTextureStageState(0, d3d9::D3DTSS_ALPHAOP, textureOp); + } + + const bool validColorKey = surface6->GetCommonSurface()->HasValidColorKey(); + m_bridge->SetColorKeyState(m_colorKeyEnabled && validColorKey); + if (m_colorKeyEnabled && validColorKey) { + DDCOLORKEY normalizedColorKey = surface6->GetCommonSurface()->GetColorKeyNormalized(); + m_bridge->SetColorKey(normalizedColorKey.dwColorSpaceLowValue, + normalizedColorKey.dwColorSpaceHighValue); + } + } + } + + m_textures[stage] = texture6; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Device::GetTextureStageState(DWORD dwStage, D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, LPDWORD lpdwState) { + Logger::debug(">>> D3D6Device::GetTextureStageState"); + + if (unlikely(lpdwState == nullptr)) + return DDERR_INVALIDPARAMS; + + // In the case of D3DTSS_ADDRESS, which is D3D6 specific, + // simply return based on D3DTSS_ADDRESSU + if (d3dTexStageStateType == D3DTSS_ADDRESS) { + return m_d3d9->GetSamplerState(dwStage, d3d9::D3DSAMP_ADDRESSU, lpdwState); + } + + d3d9::D3DSAMPLERSTATETYPE stateType = ConvertSamplerStateType(d3dTexStageStateType); + + // If the type has been remapped to a sampler state type + if (stateType != -1u) { + // MAG/MIN/MIP filter enums are each different than the unified D3D9 D3DTEXTUREFILTERTYPE + if (stateType == d3d9::D3DSAMP_MAGFILTER || + stateType == d3d9::D3DSAMP_MINFILTER || stateType == d3d9::D3DSAMP_MIPFILTER) { + DWORD dwStateProxy9 = 0; + HRESULT hr = m_d3d9->GetSamplerState(dwStage, stateType, &dwStateProxy9); + *lpdwState = DecodeD3D9TexFilterValues(d3dTexStageStateType, dwStateProxy9); + return hr; + } else { + return m_d3d9->GetSamplerState(dwStage, stateType, lpdwState); + } + } else { + return m_d3d9->GetTextureStageState(dwStage, d3d9::D3DTEXTURESTAGESTATETYPE(d3dTexStageStateType), lpdwState); + } + } + + HRESULT STDMETHODCALLTYPE D3D6Device::SetTextureStageState(DWORD dwStage, D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, DWORD dwState) { + Logger::debug(">>> D3D6Device::SetTextureStageState"); + + // In the case of D3DTSS_ADDRESS, which is D3D6 specific, + // we need to set up both D3DTSS_ADDRESSU and D3DTSS_ADDRESSV + if (d3dTexStageStateType == D3DTSS_ADDRESS) { + m_d3d9->SetSamplerState(dwStage, d3d9::D3DSAMP_ADDRESSU, dwState); + return m_d3d9->SetSamplerState(dwStage, d3d9::D3DSAMP_ADDRESSV, dwState); + } + + if (!m_alphaOpSet && d3dTexStageStateType == D3DTSS_ALPHAOP) + m_alphaOpSet = true; + + d3d9::D3DSAMPLERSTATETYPE stateType = ConvertSamplerStateType(d3dTexStageStateType); + + // If the type has been remapped to a sampler state type + if (stateType != -1u) { + // MAG/MIN/MIP filter enums are each different than the unified D3D9 D3DTEXTUREFILTERTYPE + if (stateType == d3d9::D3DSAMP_MAGFILTER || + stateType == d3d9::D3DSAMP_MINFILTER || stateType == d3d9::D3DSAMP_MIPFILTER) { + const DWORD dwState9 = DecodeD3D7TexFilterValues(d3dTexStageStateType, dwState); + return m_d3d9->SetSamplerState(dwStage, stateType, dwState9); + } else { + return m_d3d9->SetSamplerState(dwStage, stateType, dwState); + } + } else { + return m_d3d9->SetTextureStageState(dwStage, d3d9::D3DTEXTURESTAGESTATETYPE(d3dTexStageStateType), dwState); + } + } + + HRESULT STDMETHODCALLTYPE D3D6Device::ValidateDevice(LPDWORD lpdwPasses) { + Logger::debug(">>> D3D6Device::ValidateDevice"); + + HRESULT hr = m_d3d9->ValidateDevice(lpdwPasses); + if (unlikely(FAILED(hr))) + return DDERR_INVALIDPARAMS; + + return D3D_OK; + } + + void D3D6Device::InitializeDS() { + if (!m_rt->IsInitialized()) + m_rt->InitializeD3D9RenderTarget(); + + m_ds = m_rt->GetAttachedDepthStencil(); + + if (m_ds != nullptr) { + Logger::debug("D3D6Device::InitializeDS: Found an attached DS"); + + HRESULT hrDS = m_ds->InitializeD3D9DepthStencil(); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D6Device::InitializeDS: Failed to initialize D3D9 DS"); + } else { + Logger::info("D3D6Device::InitializeDS: Got depth stencil from RT"); + + DDSURFACEDESC2 descDS; + descDS.dwSize = sizeof(DDSURFACEDESC2); + m_ds->GetProxied()->GetSurfaceDesc(&descDS); + Logger::debug(str::format("D3D6Device::InitializeDS: DepthStencil: ", descDS.dwWidth, "x", descDS.dwHeight)); + + HRESULT hrDS9 = m_d3d9->SetDepthStencilSurface(m_ds->GetD3D9()); + if(unlikely(FAILED(hrDS9))) { + Logger::err("D3D6Device::InitializeDS: Failed to set D3D9 depth stencil"); + } else { + // This needs to act like an auto depth stencil of sorts, so manually enable z-buffering + m_d3d9->SetRenderState(d3d9::D3DRS_ZENABLE, d3d9::D3DZB_TRUE); + } + } + } else { + Logger::info("D3D6Device::InitializeDS: RT has no depth stencil attached"); + m_d3d9->SetDepthStencilSurface(nullptr); + // Should be superfluous, but play it safe + m_d3d9->SetRenderState(d3d9::D3DRS_ZENABLE, d3d9::D3DZB_FALSE); + } + } + + HRESULT D3D6Device::ResetD3D9Swapchain(d3d9::D3DPRESENT_PARAMETERS* params) { + Logger::info("D3D6Device::ResetD3D9Swapchain: Resetting the D3D9 swapchain"); + + HRESULT hr = m_bridge->ResetSwapChain(params); + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Device::ResetD3D9Swapchain: Failed to reset the D3D9 swapchain"); + } else { + // TODO: Cache and reset all surfaces tied to the D3D9 backbuffers + m_rt->SetD3D9(nullptr); + // Note that the D3D9 depth stencil survives a swapchain reset, + // so there's no need to worry about it in this case + } + + return hr; + } + + inline HRESULT D3D6Device::InitializeIndexBuffers() { + static constexpr DWORD Usage = D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY; + + for (uint8_t ibIndex = 0; ibIndex < ddrawCaps::IndexBufferCount ; ibIndex++) { + const UINT ibSize = ddrawCaps::IndexCount[ibIndex] * sizeof(WORD); + + Logger::debug(str::format("D3D6Device::InitializeIndexBuffer: Creating index buffer, size: ", ibSize)); + + HRESULT hr = m_d3d9->CreateIndexBuffer(ibSize, Usage, d3d9::D3DFMT_INDEX16, + d3d9::D3DPOOL_DEFAULT, &m_ib9[ibIndex], nullptr); + if (unlikely(FAILED(hr))) + return hr; + } + + return D3D_OK; + } + + inline void D3D6Device::UploadIndices(d3d9::IDirect3DIndexBuffer9* ib9, WORD* indices, DWORD indexCount) { + Logger::debug(str::format("D3D6Device::UploadIndices: Uploading ", indexCount, " indices")); + + const size_t size = indexCount * sizeof(WORD); + void* pData = nullptr; + + // Locking and unlocking are generally expected to work here + ib9->Lock(0, size, &pData, D3DLOCK_DISCARD); + memcpy(pData, static_cast(indices), size); + ib9->Unlock(); + } + + inline void D3D6Device::AddViewportInternal(IDirect3DViewport3* viewport) { + D3D6Viewport* d3d6Viewport = static_cast(viewport); + + auto it = std::find(m_viewports.begin(), m_viewports.end(), d3d6Viewport); + if (unlikely(it != m_viewports.end())) { + Logger::warn("D3D6Device::AddViewportInternal: Pre-existing viewport found"); + } else { + m_viewports.push_back(d3d6Viewport); + d3d6Viewport->GetCommonViewport()->SetD3D6Device(this); + } + } + + inline void D3D6Device::DeleteViewportInternal(IDirect3DViewport3* viewport) { + D3D6Viewport* d3d6Viewport = static_cast(viewport); + + auto it = std::find(m_viewports.begin(), m_viewports.end(), d3d6Viewport); + if (likely(it != m_viewports.end())) { + m_viewports.erase(it); + d3d6Viewport->GetCommonViewport()->SetD3D6Device(nullptr); + } else { + Logger::warn("D3D6Device::DeleteViewportInternal: Viewport not found"); + } + } + +} diff --git a/src/ddraw/d3d6/d3d6_device.h b/src/ddraw/d3d6/d3d6_device.h new file mode 100644 index 00000000000..ea45dc85280 --- /dev/null +++ b/src/ddraw/d3d6/d3d6_device.h @@ -0,0 +1,285 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_options.h" +#include "../ddraw_util.h" +#include "../ddraw_caps.h" + +#include "../d3d_multithread.h" +#include "../ddraw_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +#include "d3d6_interface.h" +#include "d3d6_viewport.h" + +#include +#include + +namespace dxvk { + + class DDrawCommonInterface; + class DDraw4Surface; + class DDraw4Interface; + class D3D6Texture; + + /** + * \brief D3D6 device implementation + */ + class D3D6Device final : public DDrawWrappedObject { + + public: + D3D6Device( + Com&& d3d6DeviceProxy, + D3D6Interface* pParent, + D3DDEVICEDESC Desc, + GUID deviceGUID, + d3d9::D3DPRESENT_PARAMETERS Params9, + Com&& pDevice9, + DDraw4Surface* pRT, + DWORD CreationFlags9); + + ~D3D6Device(); + + HRESULT STDMETHODCALLTYPE GetCaps(D3DDEVICEDESC *hal_desc, D3DDEVICEDESC *hel_desc); + + HRESULT STDMETHODCALLTYPE GetStats(D3DSTATS *stats); + + HRESULT STDMETHODCALLTYPE AddViewport(IDirect3DViewport3 *viewport); + + HRESULT STDMETHODCALLTYPE DeleteViewport(IDirect3DViewport3 *viewport); + + HRESULT STDMETHODCALLTYPE NextViewport(IDirect3DViewport3 *lpDirect3DViewport, IDirect3DViewport3 **lplpAnotherViewport, DWORD flags); + + HRESULT STDMETHODCALLTYPE EnumTextureFormats(LPD3DENUMPIXELFORMATSCALLBACK cb, void *ctx); + + HRESULT STDMETHODCALLTYPE BeginScene(); + + HRESULT STDMETHODCALLTYPE EndScene(); + + HRESULT STDMETHODCALLTYPE GetDirect3D(IDirect3D3 **d3d); + + HRESULT STDMETHODCALLTYPE SetCurrentViewport(IDirect3DViewport3 *viewport); + + HRESULT STDMETHODCALLTYPE GetCurrentViewport(IDirect3DViewport3 **viewport); + + HRESULT STDMETHODCALLTYPE SetRenderTarget(IDirectDrawSurface4 *surface, DWORD flags); + + HRESULT STDMETHODCALLTYPE GetRenderTarget(IDirectDrawSurface4 **surface); + + HRESULT STDMETHODCALLTYPE Begin(D3DPRIMITIVETYPE d3dptPrimitiveType, DWORD dwVertexTypeDesc, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE BeginIndexed(D3DPRIMITIVETYPE primitive_type, DWORD fvf, void *vertices, DWORD vertex_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE Vertex(void *vertex); + + HRESULT STDMETHODCALLTYPE Index(WORD wVertexIndex); + + HRESULT STDMETHODCALLTYPE End(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetRenderState(D3DRENDERSTATETYPE dwRenderStateType, LPDWORD lpdwRenderState); + + HRESULT STDMETHODCALLTYPE SetRenderState(D3DRENDERSTATETYPE dwRenderStateType, DWORD dwRenderState); + + HRESULT STDMETHODCALLTYPE GetLightState(D3DLIGHTSTATETYPE dwLightStateType, LPDWORD lpdwLightState); + + HRESULT STDMETHODCALLTYPE SetLightState(D3DLIGHTSTATETYPE dwLightStateType, DWORD dwLightState); + + HRESULT STDMETHODCALLTYPE SetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE GetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE MultiplyTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE DrawPrimitive(D3DPRIMITIVETYPE primitive_type, DWORD vertex_type, void *vertices, DWORD vertex_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitive(D3DPRIMITIVETYPE primitive_type, DWORD fvf, void *vertices, DWORD vertex_count, WORD *indices, DWORD index_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE SetClipStatus(D3DCLIPSTATUS *clip_status); + + HRESULT STDMETHODCALLTYPE GetClipStatus(D3DCLIPSTATUS *clip_status); + + HRESULT STDMETHODCALLTYPE DrawPrimitiveStrided(D3DPRIMITIVETYPE primitive_type, DWORD fvf, D3DDRAWPRIMITIVESTRIDEDDATA *strided_data, DWORD vertex_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitiveStrided(D3DPRIMITIVETYPE primitive_type, DWORD fvf, D3DDRAWPRIMITIVESTRIDEDDATA *strided_data, DWORD vertex_count, WORD *indices, DWORD index_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE DrawPrimitiveVB(D3DPRIMITIVETYPE primitive_type, IDirect3DVertexBuffer *vb, DWORD start_vertex, DWORD vertex_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitiveVB(D3DPRIMITIVETYPE primitive_type, IDirect3DVertexBuffer *vb, WORD *indices, DWORD index_count, DWORD flags); + + HRESULT STDMETHODCALLTYPE ComputeSphereVisibility(D3DVECTOR *lpCenters, D3DVALUE *lpRadii, DWORD dwNumSpheres, DWORD dwFlags, DWORD *lpdwReturnValues); + + HRESULT STDMETHODCALLTYPE GetTexture(DWORD stage, IDirect3DTexture2 **texture); + + HRESULT STDMETHODCALLTYPE SetTexture(DWORD stage, IDirect3DTexture2 *texture); + + HRESULT STDMETHODCALLTYPE GetTextureStageState(DWORD dwStage, D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, LPDWORD lpdwState); + + HRESULT STDMETHODCALLTYPE SetTextureStageState(DWORD dwStage, D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, DWORD dwState); + + HRESULT STDMETHODCALLTYPE ValidateDevice(LPDWORD lpdwPasses); + + void InitializeDS(); + + HRESULT ResetD3D9Swapchain(d3d9::D3DPRESENT_PARAMETERS* params); + + D3DDeviceLock LockDevice() { + return m_multithread.AcquireLock(); + } + + void EnableLegacyLights(bool isD3DLight2) { + m_bridge->SetLegacyLightsState(true, isD3DLight2); + } + + uint32_t GetTotalTextureMemory() const { + return m_totalMemory; + } + + d3d9::D3DPRESENT_PARAMETERS GetPresentParameters() const { + return m_params9; + } + + d3d9::D3DMULTISAMPLE_TYPE GetMultiSampleType() const { + return m_params9.MultiSampleType; + } + + DDraw4Surface* GetRenderTarget() const { + return m_rt.ptr(); + } + + DDraw4Surface* GetDepthStencil() const { + return m_ds.ptr(); + } + + D3D6Viewport* GetCurrentViewportInternal() const { + return m_currentViewport.ptr(); + } + + D3DMATERIALHANDLE GetCurrentMaterialHandle() const { + return m_materialHandle; + } + + private: + + inline HRESULT InitializeIndexBuffers(); + + inline void UploadIndices(d3d9::IDirect3DIndexBuffer9* ib9, WORD* indices, DWORD indexCount); + + inline void AddViewportInternal(IDirect3DViewport3* viewport); + + inline void DeleteViewportInternal(IDirect3DViewport3* viewport); + + inline bool LogIndexBufferUsageStats() const { + for (uint32_t m_ib9_upload : m_ib9_uploads) { + if (m_ib9_upload > 0) + return true; + } + return false; + } + + inline void RefreshLastUsedDevice() { + if (unlikely(m_commonIntf->GetD3D6Device() != this)) + m_commonIntf->SetD3D6Device(this); + } + + inline void HandlePreDrawFlags(DWORD drawFlags, DWORD vertexTypeDesc) { + // Docs: "Direct3D normally performs lighting calculations + // on any vertices that contain a vertex normal." + if (m_materialHandle == 0 || + (drawFlags & D3DDP_DONOTLIGHT) || + !(vertexTypeDesc & D3DFVF_NORMAL)) { + m_d3d9->GetRenderState(d3d9::D3DRS_LIGHTING, &m_lighting); + if (m_lighting) { + //Logger::debug("D3D6Device: Disabling lighting"); + m_d3d9->SetRenderState(d3d9::D3DRS_LIGHTING, FALSE); + } + } + } + + inline void HandlePostDrawFlags(DWORD drawFlags, DWORD vertexTypeDesc) { + if ((m_materialHandle == 0 || + (drawFlags & D3DDP_DONOTLIGHT) || + !(vertexTypeDesc & D3DFVF_NORMAL)) && m_lighting) { + //Logger::debug("D3D6Device: Enabling lighting"); + m_d3d9->SetRenderState(d3d9::D3DRS_LIGHTING, TRUE); + } + } + + inline void HandlePreDrawLegacyProjection(DWORD drawFlags) { + if (likely(m_currentViewport != nullptr)) { + m_legacyProjection = m_currentViewport->GetCommonViewport()->GetLegacyProjectionMatrix(drawFlags); + + if (m_legacyProjection != nullptr) { + //Logger::debug("D3D6Device: Applying legacy projection"); + m_d3d9->GetTransform(d3d9::D3DTS_PROJECTION, &m_projectionMatrix); + m_d3d9->MultiplyTransform(d3d9::D3DTS_PROJECTION, m_legacyProjection); + } + } + } + + inline void HandlePostDrawLegacyProjection() { + if (m_legacyProjection != nullptr) { + //Logger::debug("D3D6Device: Reverting legacy projection"); + m_d3d9->SetTransform(d3d9::D3DTS_PROJECTION, &m_projectionMatrix); + } + } + + bool m_inScene = false; + bool m_alphaOpSet = false; + + static uint32_t s_deviceCount; + uint32_t m_deviceCount = 0; + + uint32_t m_totalMemory = 0; + + DWORD m_lighting = FALSE; + + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_bridge; + + D3DMultithread m_multithread; + + d3d9::D3DPRESENT_PARAMETERS m_params9; + + D3DMATERIALHANDLE m_materialHandle = 0; + + D3DDEVICEDESC m_desc; + GUID m_deviceGUID; + + Com m_rt; + Com m_ds; + + Com m_currentViewport; + std::vector> m_viewports; + + VertexStreamInfo m_vertexStreamInfo; + std::vector m_vertexStream; + std::vector m_lvertexStream; + std::vector m_tlvertexStream; + + std::array, ddrawCaps::TextureStageCount> m_textures; + + // Value of D3DRENDERSTATE_COLORKEYENABLE + DWORD m_colorKeyEnabled = 0; + // Value of D3DRENDERSTATE_ANTIALIAS + DWORD m_antialias = D3DANTIALIAS_NONE; + // Value of D3DRENDERSTATE_LINEPATTERN + D3DLINEPATTERN m_linePattern = { }; + // Value of D3DCLIPSTATUS + D3DCLIPSTATUS m_clipStatus = { }; + // Value of D3DRENDERSTATE_TEXTUREMAPBLEND + DWORD m_textureMapBlend = D3DTBLEND_MODULATE; + + D3DMATRIX m_projectionMatrix = { }; + const D3DMATRIX* m_legacyProjection = nullptr; + + // Common index buffers used for indexed draws, split up into five sizes: + // XS, S, M, L and XL, corresponding to 0.5 kb, 2 kb, 8 kb, 32 kb and 128 kb + std::array, ddrawCaps::IndexBufferCount> m_ib9; + uint32_t m_ib9_uploads[ddrawCaps::IndexBufferCount] = { }; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d6/d3d6_interface.cpp b/src/ddraw/d3d6/d3d6_interface.cpp new file mode 100644 index 00000000000..490d1f1372c --- /dev/null +++ b/src/ddraw/d3d6/d3d6_interface.cpp @@ -0,0 +1,566 @@ +#include "d3d6_interface.h" + +#include "d3d6_device.h" +#include "d3d6_buffer.h" +#include "d3d6_material.h" +#include "d3d6_viewport.h" + +#include "../d3d_light.h" +#include "../d3d_multithread.h" + +#include "../ddraw4/ddraw4_interface.h" +#include "../ddraw4/ddraw4_surface.h" + +namespace dxvk { + + uint32_t D3D6Interface::s_intfCount = 0; + + D3D6Interface::D3D6Interface( + DDrawCommonInterface* commonIntf, + D3DCommonInterface* commonD3DIntf, + Com&& d3d6IntfProxy, + IUnknown* pParent) + : DDrawWrappedObject(pParent, std::move(d3d6IntfProxy), std::move(d3d9::Direct3DCreate9(D3D_SDK_VERSION))) + , m_commonIntf ( commonIntf ) + , m_commonD3DIntf ( commonD3DIntf ) { + // Get the bridge interface to D3D9. + if (unlikely(FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8InterfaceBridge), reinterpret_cast(&m_bridge))))) { + throw DxvkError("D3D6Interface: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + if (m_commonD3DIntf == nullptr) + m_commonD3DIntf = new D3DCommonInterface(); + + m_commonD3DIntf->SetD3D6Interface(this); + + m_bridge->EnableD3D6CompatibilityMode(); + + m_intfCount = ++s_intfCount; + + Logger::debug(str::format("D3D6Interface: Created a new interface nr. ((3-", m_intfCount, "))")); + } + + D3D6Interface::~D3D6Interface() { + if (m_commonD3DIntf->GetD3D6Interface() == this) + m_commonD3DIntf->SetD3D6Interface(nullptr); + + Logger::debug(str::format("D3D6Interface: Interface nr. ((3-", m_intfCount, ")) bites the dust")); + } + + // Interlocked refcount with the parent IDirectDraw4 + ULONG STDMETHODCALLTYPE D3D6Interface::AddRef() { + if (likely(m_parent != nullptr)) { + IUnknown* origin = m_commonIntf->GetOrigin(); + if (likely(origin != nullptr)) + return origin->AddRef(); + else + return m_parent->AddRef(); + } else { + return ComObjectClamp::AddRef(); + } + } + + // Interlocked refcount with the parent IDirectDraw4 + ULONG STDMETHODCALLTYPE D3D6Interface::Release() { + if (likely(m_parent != nullptr)) { + IUnknown* origin = m_commonIntf->GetOrigin(); + if (likely(origin != nullptr)) + return origin->Release(); + else + return m_parent->Release(); + } else { + return ComObjectClamp::Release(); + } + } + + HRESULT STDMETHODCALLTYPE D3D6Interface::QueryInterface(REFIID riid, void** ppvObject) { + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (riid == __uuidof(IDirectDraw4)) { + Logger::debug("D3D6Interface::QueryInterface: Query for IDirectDraw4"); + return m_parent->QueryInterface(riid, ppvObject); + } + // Some games query for ddraw interfaces + if (unlikely(riid == __uuidof(IDirectDraw) + || riid == __uuidof(IDirectDraw2))) { + Logger::debug("D3D6Interface::QueryInterface: Query for legacy IDirectDraw"); + return m_parent->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE D3D6Interface::EnumDevices(LPD3DENUMDEVICESCALLBACK lpEnumDevicesCallback, LPVOID lpUserArg) { + Logger::debug(">>> D3D6Interface::EnumDevices"); + + if (unlikely(lpEnumDevicesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // D3D6 reports both HAL and HEL caps for any time of device, + // with minor differences between the two. Note that the + // device listing order matters, so list RGB first, HAL second. + + // Software emulation, this is expected to be exposed + GUID guidRGB = IID_IDirect3DRGBDevice; + D3DDEVICEDESC descRGB_HAL = GetD3D6Caps(IID_IDirect3DRGBDevice, d3dOptions); + D3DDEVICEDESC descRGB_HEL = descRGB_HAL; + descRGB_HAL.dwFlags = 0; + descRGB_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + descRGB_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL + & ~D3DPTEXTURECAPS_POW2; + descRGB_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL + & ~D3DPTEXTURECAPS_POW2; + descRGB_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + descRGB_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + static char deviceDescRGB[100] = "D6VK RGB"; + static char deviceNameRGB[100] = "D6VK RGB"; + + HRESULT hr = lpEnumDevicesCallback(&guidRGB, &deviceDescRGB[0], &deviceNameRGB[0], + &descRGB_HAL, &descRGB_HEL, lpUserArg); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + // Hardware acceleration + GUID guidHAL = IID_IDirect3DHALDevice; + D3DDEVICEDESC descHAL_HAL = GetD3D6Caps(IID_IDirect3DHALDevice, d3dOptions); + D3DDEVICEDESC descHAL_HEL = descHAL_HAL; + descHAL_HEL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + descHAL_HEL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL + & ~D3DPTEXTURECAPS_POW2; + descHAL_HEL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL + & ~D3DPTEXTURECAPS_POW2; + descHAL_HEL.dwDevCaps &= ~D3DDEVCAPS_HWTRANSFORMANDLIGHT + & ~D3DDEVCAPS_DRAWPRIMITIVES2 + & ~D3DDEVCAPS_DRAWPRIMITIVES2EX; + static char deviceDescHAL[100] = "D6VK HAL"; + static char deviceNameHAL[100] = "D6VK HAL"; + + hr = lpEnumDevicesCallback(&guidHAL, &deviceDescHAL[0], &deviceNameHAL[0], + &descHAL_HAL, &descHAL_HEL, lpUserArg); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Interface::CreateLight(LPDIRECT3DLIGHT *lplpDirect3DLight, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D6Interface::CreateLight"); + + if (unlikely(lplpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DLight); + + *lplpDirect3DLight = ref(new D3DLight(nullptr, this)); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Interface::CreateMaterial(LPDIRECT3DMATERIAL3 *lplpDirect3DMaterial, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D6Interface::CreateMaterial"); + + if (unlikely(lplpDirect3DMaterial == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DMaterial); + + D3DMATERIALHANDLE handle = m_commonD3DIntf->GetNextMaterialHandle(); + Com d3d6Material = new D3D6Material(nullptr, this, handle); + m_commonD3DIntf->EmplaceMaterial(d3d6Material->GetCommonMaterial(), handle); + + *lplpDirect3DMaterial = d3d6Material.ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Interface::CreateViewport(LPDIRECT3DVIEWPORT3 *lplpD3DViewport, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D6Interface::CreateViewport"); + + Com lplpD3DViewportProxy; + HRESULT hr = m_proxy->CreateViewport(&lplpD3DViewportProxy, pUnkOuter); + if (unlikely(FAILED(hr))) + return hr; + + InitReturnPtr(lplpD3DViewport); + + *lplpD3DViewport = ref(new D3D6Viewport(nullptr, std::move(lplpD3DViewportProxy), this)); + + return D3D_OK; + } + + // Minimal implementation which should suffice in most cases + HRESULT STDMETHODCALLTYPE D3D6Interface::FindDevice(D3DFINDDEVICESEARCH *lpD3DFDS, D3DFINDDEVICERESULT *lpD3DFDR) { + Logger::debug(">>> D3D6Interface::FindDevice"); + + if (unlikely(lpD3DFDS == nullptr || lpD3DFDR == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpD3DFDS->dwSize != sizeof(D3DFINDDEVICESEARCH))) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // Software emulation, this is expected to be exposed + D3DDEVICEDESC descRGB_HAL = GetD3D6Caps(IID_IDirect3DRGBDevice, d3dOptions); + D3DDEVICEDESC descRGB_HEL = descRGB_HAL; + descRGB_HAL.dwFlags = 0; + descRGB_HAL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + descRGB_HAL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL + & ~D3DPTEXTURECAPS_POW2; + descRGB_HAL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL + & ~D3DPTEXTURECAPS_POW2; + descRGB_HEL.dpcLineCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + descRGB_HEL.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + + // Hardware acceleration + D3DDEVICEDESC descHAL_HAL = GetD3D6Caps(IID_IDirect3DHALDevice, d3dOptions); + D3DDEVICEDESC descHAL_HEL = descHAL_HAL; + descHAL_HEL.dcmColorModel = 0; + // Some applications apparently care about RGB texture caps + descHAL_HEL.dpcLineCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL + & ~D3DPTEXTURECAPS_POW2; + descHAL_HEL.dpcTriCaps.dwTextureCaps &= ~D3DPTEXTURECAPS_PERSPECTIVE + & ~D3DPTEXTURECAPS_NONPOW2CONDITIONAL + & ~D3DPTEXTURECAPS_POW2; + descHAL_HEL.dwDevCaps &= ~D3DDEVCAPS_HWTRANSFORMANDLIGHT + & ~D3DDEVCAPS_DRAWPRIMITIVES2 + & ~D3DDEVCAPS_DRAWPRIMITIVES2EX; + + lpD3DFDR->dwSize = sizeof(D3DFINDDEVICERESULT); + + if (lpD3DFDS->dwFlags & D3DFDS_GUID) { + Logger::debug("D3D6Interface::FindDevice: Matching by device GUID"); + + if (lpD3DFDS->guid == IID_IDirect3DRGBDevice || + lpD3DFDS->guid == IID_IDirect3DMMXDevice || + lpD3DFDS->guid == IID_IDirect3DRampDevice) { + Logger::debug("D3D6Interface::FindDevice: Matched IID_IDirect3DRGBDevice"); + lpD3DFDR->guid = IID_IDirect3DRGBDevice; + lpD3DFDR->ddHwDesc = descRGB_HAL; + lpD3DFDR->ddSwDesc = descRGB_HEL; + } else if (lpD3DFDS->guid == IID_IDirect3DHALDevice) { + Logger::debug("D3D6Interface::FindDevice: Matched IID_IDirect3DHALDevice"); + lpD3DFDR->guid = IID_IDirect3DHALDevice; + lpD3DFDR->ddHwDesc = descHAL_HAL; + lpD3DFDR->ddSwDesc = descHAL_HEL; + } else { + Logger::err(str::format("D3D6Interface::FindDevice: Unknown device type: ", lpD3DFDS->guid)); + return DDERR_NOTFOUND; + } + } else if (lpD3DFDS->dwFlags & D3DFDS_HARDWARE) { + Logger::debug("D3D6Interface::FindDevice: Matching by hardware flag"); + + if (likely(lpD3DFDS->bHardware == TRUE)) { + Logger::debug("D3D6Interface::FindDevice: Matched IID_IDirect3DHALDevice"); + lpD3DFDR->guid = IID_IDirect3DHALDevice; + lpD3DFDR->ddHwDesc = descHAL_HAL; + lpD3DFDR->ddSwDesc = descHAL_HEL; + } else { + Logger::debug("D3D6Interface::FindDevice: Matched IID_IDirect3DRGBDevice"); + lpD3DFDR->guid = IID_IDirect3DRGBDevice; + lpD3DFDR->ddHwDesc = descRGB_HAL; + lpD3DFDR->ddSwDesc = descRGB_HEL; + } + } else { + Logger::err("D3D6Interface::FindDevice: Unhandled matching type"); + return DDERR_NOTFOUND; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Interface::CreateDevice(REFCLSID rclsid, LPDIRECTDRAWSURFACE4 lpDDS, LPDIRECT3DDEVICE3 *lplpD3DDevice, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D6Interface::CreateDevice"); + + if (unlikely(lplpD3DDevice == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpD3DDevice); + + if (unlikely(lpDDS == nullptr)) { + Logger::err("D3D6Interface::CreateDevice: Null surface provided"); + return DDERR_INVALIDPARAMS; + } + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + DWORD deviceCreationFlags9 = D3DCREATE_SOFTWARE_VERTEXPROCESSING; + bool rgbFallback = false; + + if (likely(!d3dOptions->forceSWVP)) { + if (rclsid == IID_IDirect3DHALDevice) { + Logger::info("D3D6Interface::CreateDevice: Creating an IID_IDirect3DHALDevice device"); + deviceCreationFlags9 = D3DCREATE_MIXED_VERTEXPROCESSING; + } else if (rclsid == IID_IDirect3DRGBDevice) { + Logger::info("D3D6Interface::CreateDevice: Creating an IID_IDirect3DRGBDevice device"); + } else if (rclsid == IID_IDirect3DMMXDevice) { + Logger::warn("D3D6Interface::CreateDevice: Unsupported MMX device, falling back to RGB"); + rgbFallback = true; + } else { + // Revenant uses a rclsid of 7a31a548-0000-0007-26ed-780000000000... + Logger::warn("D3D6Interface::CreateDevice: Unsupported device type, falling back to RGB"); + Logger::warn(str::format(rclsid)); + rgbFallback = true; + } + } + + const IID rclsidOverride = rgbFallback ? IID_IDirect3DRGBDevice : rclsid; + + HWND hWnd = m_commonIntf->GetHWND(); + // Needed to sometimes safely skip intro playback on legacy devices + if (unlikely(hWnd == nullptr)) { + Logger::debug("D3D6Interface::CreateDevice: HWND is NULL"); + } + + Com rt4; + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDS))) { + Logger::err("D3D6Interface::CreateDevice: Unwrapped surface passed as RT"); + return DDERR_GENERIC; + } else { + rt4 = static_cast(lpDDS); + } + + Com d3d6DeviceProxy; + HRESULT hr = m_proxy->CreateDevice(rclsidOverride, rt4->GetProxied(), &d3d6DeviceProxy, nullptr); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D6Interface::CreateDevice: Failed to create the proxy device"); + return hr; + } + + DDSURFACEDESC2 desc; + desc.dwSize = sizeof(DDSURFACEDESC2); + lpDDS->GetSurfaceDesc(&desc); + + DWORD backBufferWidth = desc.dwWidth; + DWORD BackBufferHeight = desc.dwHeight; + + if (likely(!d3dOptions->forceProxiedPresent && + d3dOptions->backBufferResize)) { + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Ignore any mode size dimensions when in windowed present mode + if (exclusiveMode) { + DDrawModeSize* modeSize = m_commonIntf->GetModeSize(); + // Wayland apparently needs this for somewhat proper back buffer sizing + if ((modeSize->width && modeSize->width < desc.dwWidth) + || (modeSize->height && modeSize->height < desc.dwHeight)) { + Logger::info("D3D6Interface::CreateDevice: Enforcing mode dimensions"); + + backBufferWidth = modeSize->width; + BackBufferHeight = modeSize->height; + } + } + } + + d3d9::D3DFORMAT backBufferFormat = ConvertFormat(desc.ddpfPixelFormat); + + // Determine the supported AA sample count by querying the D3D9 interface + d3d9::D3DMULTISAMPLE_TYPE multiSampleType = d3d9::D3DMULTISAMPLE_NONE; + if (likely(d3dOptions->emulateFSAA != FSAAEmulation::Disabled)) { + HRESULT hr4S = m_d3d9->CheckDeviceMultiSampleType(0, d3d9::D3DDEVTYPE_HAL, backBufferFormat, + TRUE, d3d9::D3DMULTISAMPLE_4_SAMPLES, NULL); + if (unlikely(FAILED(hr4S))) { + HRESULT hr2S = m_d3d9->CheckDeviceMultiSampleType(0, d3d9::D3DDEVTYPE_HAL, backBufferFormat, + TRUE, d3d9::D3DMULTISAMPLE_2_SAMPLES, NULL); + if (unlikely(FAILED(hr2S))) { + Logger::warn("D3D6Interface::CreateDevice: No MSAA support has been detected"); + } else { + Logger::info("D3D6Interface::CreateDevice: Using 2x MSAA for FSAA emulation"); + multiSampleType = d3d9::D3DMULTISAMPLE_2_SAMPLES; + } + } else { + Logger::info("D3D6Interface::CreateDevice: Using 4x MSAA for FSAA emulation"); + multiSampleType = d3d9::D3DMULTISAMPLE_4_SAMPLES; + } + } else { + Logger::info("D3D6Interface::CreateDevice: FSAA emulation is disabled"); + } + + const DWORD cooperativeLevel = m_commonIntf->GetCooperativeLevel(); + + if ((cooperativeLevel & DDSCL_MULTITHREADED) || d3dOptions->forceMultiThreaded) { + Logger::info("D3D6Interface::CreateDevice: Using thread safe runtime synchronization"); + deviceCreationFlags9 |= D3DCREATE_MULTITHREADED; + } + // DDSCL_FPUPRESERVE does not exist prior to DDraw7, + // and DDSCL_FPUSETUP is NOT the default state + if (!(cooperativeLevel & DDSCL_FPUSETUP)) + deviceCreationFlags9 |= D3DCREATE_FPU_PRESERVE; + if (cooperativeLevel & DDSCL_NOWINDOWCHANGES) + deviceCreationFlags9 |= D3DCREATE_NOWINDOWCHANGES; + + Logger::info(str::format("D3D6Interface::CreateDevice: Back buffer size: ", desc.dwWidth, "x", desc.dwHeight)); + + DWORD backBufferCount = 0; + if (likely(!d3dOptions->forceSingleBackBuffer)) { + IDirectDrawSurface4* backBuffer = rt4->GetProxied(); + while (backBuffer != nullptr) { + IDirectDrawSurface4* parentSurface = backBuffer; + backBuffer = nullptr; + parentSurface->EnumAttachedSurfaces(&backBuffer, ListBackBufferSurfaces4Callback); + backBufferCount++; + // the swapchain will eventually return to its origin + if (backBuffer == rt4->GetProxied()) + break; + } + } + // Consider the front buffer as well when reporting the overall count + Logger::info(str::format("D3D6Interface::CreateDevice: Back buffer count: ", backBufferCount + 1)); + + // Always appears to be enabled when running in non-exclusive mode + const bool vBlankStatus = m_commonIntf->GetWaitForVBlank(); + + d3d9::D3DPRESENT_PARAMETERS params; + params.BackBufferWidth = backBufferWidth; + params.BackBufferHeight = BackBufferHeight; + params.BackBufferFormat = backBufferFormat; + params.BackBufferCount = backBufferCount; + params.MultiSampleType = multiSampleType; // Controlled through D3DRENDERSTATE_ANTIALIAS + params.MultiSampleQuality = 0; + params.SwapEffect = d3d9::D3DSWAPEFFECT_DISCARD; + params.hDeviceWindow = hWnd; + params.Windowed = TRUE; // Always use windowed, so that we can delegate mode switching to ddraw + params.EnableAutoDepthStencil = FALSE; + params.AutoDepthStencilFormat = d3d9::D3DFMT_UNKNOWN; + params.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; // Needed for back buffer locks + params.FullScreen_RefreshRateInHz = 0; // We'll get the right mode/refresh rate set by ddraw, just play along + params.PresentationInterval = vBlankStatus ? D3DPRESENT_INTERVAL_DEFAULT : D3DPRESENT_INTERVAL_IMMEDIATE; + + Com device9; + hr = m_d3d9->CreateDevice( + D3DADAPTER_DEFAULT, + d3d9::D3DDEVTYPE_HAL, + hWnd, + deviceCreationFlags9, + ¶ms, + &device9 + ); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Interface::CreateDevice: Failed to create the D3D9 device"); + return hr; + } + + D3DDEVICEDESC desc6 = GetD3D6Caps(rclsidOverride, d3dOptions); + + try{ + Com device6 = new D3D6Device(std::move(d3d6DeviceProxy), this, desc6, + rclsidOverride, params, std::move(device9), + rt4.ptr(), deviceCreationFlags9); + + // Set the newly created D3D6 device on the common interface + m_commonIntf->SetD3D6Device(device6.ptr()); + // Now that we have a valid D3D9 device pointer, we can initialize the depth stencil (if any) + device6->InitializeDS(); + + *lplpD3DDevice = device6.ref(); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Interface::CreateVertexBuffer(LPD3DVERTEXBUFFERDESC lpVBDesc, LPDIRECT3DVERTEXBUFFER *lpD3DVertexBuffer, DWORD dwFlags, IUnknown *pUnkOuter) { + Logger::debug(">>> D3D6Interface::CreateVertexBuffer"); + + if (unlikely(lpVBDesc == nullptr || lpD3DVertexBuffer == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lpD3DVertexBuffer); + + Com vertexBuffer; + // We don't really need a proxy buffer any longer + /*HRESULT hr = m_proxy->CreateVertexBuffer(desc, &vertexBuffer, usage); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D6Interface::CreateVertexBuffer: Failed to create proxy vertex buffer"); + return hr; + }*/ + + // We need to delay the D3D9 vertex buffer creation as long as possible, to ensure + // that (ideally) we actually have a valid D3D6 device in place when that happens + *lpD3DVertexBuffer = ref(new D3D6VertexBuffer(std::move(vertexBuffer), nullptr, this, dwFlags, *lpVBDesc)); + + return D3D_OK; + } + + // Total Club Manager 2003 uses a D3D6 interface to query for supported Z buffer formats, + // so report what we know is supported by D3D9, otherwise the game will error out on startup + HRESULT STDMETHODCALLTYPE D3D6Interface::EnumZBufferFormats(REFCLSID riidDevice, LPD3DENUMPIXELFORMATSCALLBACK lpEnumCallback, LPVOID lpContext) { + Logger::debug(">>> D3D6Interface::EnumZBufferFormats"); + + if (unlikely(lpEnumCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // There are just 3 supported depth stencil formats to worry about + // in D3D9, so let's just enumerate them liniarly, for better clarity + DDPIXELFORMAT depthFormat; + HRESULT hr; + + if (likely(d3dOptions->supportD16)) { + depthFormat = GetZBufferFormat(d3d9::D3DFMT_D16); + hr = lpEnumCallback(&depthFormat, lpContext); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + } + + depthFormat = GetZBufferFormat(d3d9::D3DFMT_D24X8); + hr = lpEnumCallback(&depthFormat, lpContext); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + depthFormat = GetZBufferFormat(d3d9::D3DFMT_D24S8); + hr = lpEnumCallback(&depthFormat, lpContext); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Interface::EvictManagedTextures() { + Logger::debug(">>> D3D6Interface::EvictManagedTextures"); + + HRESULT hr = m_proxy->EvictManagedTextures(); + if (unlikely(FAILED(hr))) + return hr; + + D3D6Device* d3d6Device = m_commonIntf->GetD3D6Device(); + if (likely(d3d6Device != nullptr)) { + D3DDeviceLock lock = d3d6Device->LockDevice(); + + HRESULT hr9 = d3d6Device->GetD3D9()->EvictManagedResources(); + if (unlikely(FAILED(hr9))) { + Logger::err("D3D6Interface::EvictManagedTextures: Failed D3D9 managed resource eviction"); + return hr9; + } + } + + return D3D_OK; + } + +} \ No newline at end of file diff --git a/src/ddraw/d3d6/d3d6_interface.h b/src/ddraw/d3d6/d3d6_interface.h new file mode 100644 index 00000000000..b06940023c2 --- /dev/null +++ b/src/ddraw/d3d6/d3d6_interface.h @@ -0,0 +1,75 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_options.h" +#include "../ddraw_util.h" +#include "../ddraw_format.h" + +#include "../ddraw_common_interface.h" +#include "../d3d_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +namespace dxvk { + + /** + * \brief D3D6 interface implementation + */ + class D3D6Interface final : public DDrawWrappedObject { + + public: + D3D6Interface( + DDrawCommonInterface* commonIntf, + D3DCommonInterface* commonD3DIntf, + Com&& d3d6Intf, + IUnknown* pParent); + + ~D3D6Interface(); + + ULONG STDMETHODCALLTYPE AddRef(); + + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE EnumDevices(LPD3DENUMDEVICESCALLBACK lpEnumDevicesCallback, LPVOID lpUserArg); + + HRESULT STDMETHODCALLTYPE CreateLight(LPDIRECT3DLIGHT *lplpDirect3DLight, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateMaterial(LPDIRECT3DMATERIAL3 *lplpDirect3DMaterial, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateViewport(LPDIRECT3DVIEWPORT3 *lplpD3DViewport, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE FindDevice(D3DFINDDEVICESEARCH *lpD3DFDS, D3DFINDDEVICERESULT *lpD3DFDR); + + HRESULT STDMETHODCALLTYPE CreateDevice(REFCLSID rclsid, LPDIRECTDRAWSURFACE4 lpDDS, LPDIRECT3DDEVICE3 *lplpD3DDevice, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateVertexBuffer(LPD3DVERTEXBUFFERDESC lpVBDesc, LPDIRECT3DVERTEXBUFFER *lpD3DVertexBuffer, DWORD dwFlags, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE EnumZBufferFormats(REFCLSID riidDevice, LPD3DENUMPIXELFORMATSCALLBACK lpEnumCallback, LPVOID lpContext); + + HRESULT STDMETHODCALLTYPE EvictManagedTextures(); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + D3DCommonInterface* GetCommonD3DInterface() const { + return m_commonD3DIntf.ptr(); + } + + private: + + static uint32_t s_intfCount; + uint32_t m_intfCount = 0; + + Com m_bridge; + + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_commonD3DIntf; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d6/d3d6_material.cpp b/src/ddraw/d3d6/d3d6_material.cpp new file mode 100644 index 00000000000..040318f43b3 --- /dev/null +++ b/src/ddraw/d3d6/d3d6_material.cpp @@ -0,0 +1,95 @@ +#include "d3d6_material.h" + +#include "d3d6_device.h" +#include "d3d6_interface.h" +#include "d3d6_viewport.h" + +#include "../ddraw4/ddraw4_interface.h" + +namespace dxvk { + + uint32_t D3D6Material::s_materialCount = 0; + + D3D6Material::D3D6Material( + Com&& proxyMaterial, + D3D6Interface* pParent, + D3DMATERIALHANDLE handle) + : DDrawWrappedObject(pParent, std::move(proxyMaterial), nullptr) { + m_commonMaterial = new D3DCommonMaterial(handle); + + m_materialCount = ++s_materialCount; + + Logger::debug(str::format("D3D6Material: Created a new material nr. [[3-", m_materialCount, "]]")); + } + + D3D6Material::~D3D6Material() { + m_parent->GetCommonD3DInterface()->ReleaseMaterialHandle(m_commonMaterial->GetMaterialHandle()); + + Logger::debug(str::format("D3D6Material: Material nr. [[3-", m_materialCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D6Material::SetMaterial(D3DMATERIAL *data) { + Logger::debug(">>> D3D6Material::SetMaterial"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + d3d9::D3DMATERIAL9* material9 = m_commonMaterial->GetD3D9Material(); + + material9->Diffuse = data->dcvDiffuse; + material9->Ambient = data->dcvAmbient; + material9->Specular = data->dcvSpecular; + material9->Emissive = data->dcvEmissive; + material9->Power = data->dvPower; + + D3DMATERIALHANDLE handle = m_commonMaterial->GetMaterialHandle(); + + Logger::debug(str::format(">>> D3D6Material::SetMaterial: Updated material nr. ", handle)); + Logger::debug(str::format(" Diffuse: ", material9->Diffuse.r, " ", material9->Diffuse.g, " ", material9->Diffuse.b)); + Logger::debug(str::format(" Ambient: ", material9->Ambient.r, " ", material9->Ambient.g, " ", material9->Ambient.b)); + Logger::debug(str::format(" Specular: ", material9->Specular.r, " ", material9->Specular.g, " ", material9->Specular.b)); + Logger::debug(str::format(" Emissive: ", material9->Emissive.r, " ", material9->Emissive.g, " ", material9->Emissive.b)); + Logger::debug(str::format(" Power: ", material9->Power)); + + // Update the D3D9 material directly if it's actively being used + D3D6Device* device6 = m_parent->GetCommonInterface()->GetD3D6Device(); + if (likely(device6 != nullptr)) { + D3DMATERIALHANDLE currentHandle = device6->GetCurrentMaterialHandle(); + if (currentHandle == handle) { + Logger::debug(str::format("D3D6Material::SetMaterial: Applying material nr. ", handle, " to D3D9")); + device6->GetD3D9()->SetMaterial(material9); + } + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Material::GetMaterial(D3DMATERIAL *data) { + Logger::debug(">>> D3D6Material::GetMaterial"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + d3d9::D3DMATERIAL9* material9 = m_commonMaterial->GetD3D9Material(); + + data->dcvDiffuse = material9->Diffuse; + data->dcvAmbient = material9->Ambient; + data->dcvSpecular = material9->Specular; + data->dcvEmissive = material9->Emissive; + data->dvPower = material9->Power; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Material::GetHandle(IDirect3DDevice3 *device, D3DMATERIALHANDLE *handle) { + Logger::debug(">>> D3D6Material::GetHandle"); + + if(unlikely(device == nullptr || handle == nullptr)) + return DDERR_INVALIDPARAMS; + + *handle = m_commonMaterial->GetMaterialHandle(); + + return D3D_OK; + } + +} diff --git a/src/ddraw/d3d6/d3d6_material.h b/src/ddraw/d3d6/d3d6_material.h new file mode 100644 index 00000000000..00687cbafd1 --- /dev/null +++ b/src/ddraw/d3d6/d3d6_material.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../d3d_common_material.h" + +namespace dxvk { + + class D3D6Interface; + + class D3D6Material final : public DDrawWrappedObject { + + public: + + D3D6Material( + Com&& proxyMaterial, + D3D6Interface* pParent, + D3DMATERIALHANDLE handle); + + ~D3D6Material(); + + HRESULT STDMETHODCALLTYPE SetMaterial(D3DMATERIAL *data); + + HRESULT STDMETHODCALLTYPE GetMaterial(D3DMATERIAL *data); + + HRESULT STDMETHODCALLTYPE GetHandle(IDirect3DDevice3 *device, D3DMATERIALHANDLE *handle); + + D3DCommonMaterial* GetCommonMaterial() const { + return m_commonMaterial.ptr(); + } + + private: + + static uint32_t s_materialCount; + uint32_t m_materialCount = 0; + + Com m_commonMaterial; + + }; + +} diff --git a/src/ddraw/d3d6/d3d6_texture.cpp b/src/ddraw/d3d6/d3d6_texture.cpp new file mode 100644 index 00000000000..a2f14f14003 --- /dev/null +++ b/src/ddraw/d3d6/d3d6_texture.cpp @@ -0,0 +1,128 @@ +#include "d3d6_texture.h" + +#include "../ddraw4/ddraw4_surface.h" + +namespace dxvk { + + uint32_t D3D6Texture::s_texCount = 0; + + D3D6Texture::D3D6Texture( + Com&& proxyTexture, + DDraw4Surface* pParent, + D3DTEXTUREHANDLE handle) + : DDrawWrappedObject(pParent, std::move(proxyTexture), nullptr) { + m_commonTex = new D3DCommonTexture(m_parent->GetCommonSurface(), handle); + + m_texCount = ++s_texCount; + + Logger::debug(str::format("D3D6Texture: Created a new texture nr. [[2-", m_texCount, "]]")); + } + + D3D6Texture::~D3D6Texture() { + Logger::debug(str::format("D3D6Texture: Texture nr. [[2-", m_texCount, "]] bites the dust")); + } + + // Interlocked refcount with the parent IDirectDrawSurface4 + ULONG STDMETHODCALLTYPE D3D6Texture::AddRef() { + return m_parent->AddRef(); + } + + // Interlocked refcount with the parent IDirectDrawSurface4 + ULONG STDMETHODCALLTYPE D3D6Texture::Release() { + return m_parent->Release(); + } + + HRESULT STDMETHODCALLTYPE D3D6Texture::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> D3D6Texture::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(riid == __uuidof(IDirect3DTexture))) { + Logger::debug("D3D6Texture::QueryInterface: Query for IDirect3DTexture"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawGammaControl))) { + Logger::debug("D3D6Texture::QueryInterface: Query for IDirectDrawGammaControl"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawColorControl))) { + Logger::debug("D3D6Texture::QueryInterface: Query for IDirectDrawColorControl"); + return E_NOINTERFACE; + } + if (unlikely(riid == __uuidof(IUnknown) + || riid == __uuidof(IDirectDrawSurface))) { + Logger::debug("D3D6Texture::QueryInterface: Query for IDirectDrawSurface"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface2))) { + Logger::debug("D3D6Texture::QueryInterface: Query for IDirectDrawSurface2"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface3))) { + Logger::debug("D3D6Texture::QueryInterface: Query for IDirectDrawSurface3"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface4))) { + Logger::debug("D3D6Texture::QueryInterface: Query for IDirectDrawSurface4"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface7))) { + Logger::debug("D3D6Texture::QueryInterface: Query for IDirectDrawSurface7"); + return m_parent->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE D3D6Texture::GetHandle(LPDIRECT3DDEVICE2 lpDirect3DDevice2, LPD3DTEXTUREHANDLE lpHandle) { + Logger::debug(">>> D3D6Texture::GetHandle"); + + if(unlikely(lpDirect3DDevice2 == nullptr || lpHandle == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpHandle = m_commonTex->GetTextureHandle(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Texture::PaletteChanged(DWORD dwStart, DWORD dwCount) { + Logger::warn("<<< D3D6Texture::PaletteChanged: Proxy"); + return m_proxy->PaletteChanged(dwStart, dwCount); + } + + HRESULT STDMETHODCALLTYPE D3D6Texture::Load(LPDIRECT3DTEXTURE2 lpD3DTexture2) { + Logger::debug("<<< D3D6Texture::Load: Proxy"); + + Com d3d6Texture = static_cast(lpD3DTexture2); + + HRESULT hr = m_proxy->Load(d3d6Texture->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + // Update the cached parent surface desc + DDSURFACEDESC2 desc2; + desc2.dwSize = sizeof(DDSURFACEDESC2); + HRESULT hrDesc = m_parent->GetProxied()->GetSurfaceDesc(&desc2); + + if (unlikely(FAILED(hrDesc))) { + Logger::err("D3D6Texture::Load: Failed to retrieve updated surface desc"); + } else { + m_parent->GetCommonSurface()->SetDesc2(desc2); + } + + m_parent->GetCommonSurface()->DirtyMipMaps(); + + return hr; + } + +} diff --git a/src/ddraw/d3d6/d3d6_texture.h b/src/ddraw/d3d6/d3d6_texture.h new file mode 100644 index 00000000000..30b336ac430 --- /dev/null +++ b/src/ddraw/d3d6/d3d6_texture.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../d3d_common_texture.h" + +namespace dxvk { + + class DDraw4Surface; + + class D3D6Texture final : public DDrawWrappedObject { + + public: + + D3D6Texture( + Com&& proxyTexture, + DDraw4Surface* pParent, + D3DTEXTUREHANDLE handle); + + ~D3D6Texture(); + + ULONG STDMETHODCALLTYPE AddRef(); + + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE GetHandle(LPDIRECT3DDEVICE2 lpDirect3DDevice2, LPD3DTEXTUREHANDLE lpHandle); + + HRESULT STDMETHODCALLTYPE PaletteChanged(DWORD dwStart, DWORD dwCount); + + HRESULT STDMETHODCALLTYPE Load(LPDIRECT3DTEXTURE2 lpD3DTexture2); + + D3DCommonTexture* GetCommonTexture() const { + return m_commonTex.ptr(); + } + + private: + + static uint32_t s_texCount; + uint32_t m_texCount = 0; + + Com m_commonTex; + + }; + +} diff --git a/src/ddraw/d3d6/d3d6_viewport.cpp b/src/ddraw/d3d6/d3d6_viewport.cpp new file mode 100644 index 00000000000..8dfdc592241 --- /dev/null +++ b/src/ddraw/d3d6/d3d6_viewport.cpp @@ -0,0 +1,575 @@ +#include "d3d6_viewport.h" + +#include "d3d6_device.h" + +#include "../d3d_light.h" +#include "../d3d_common_material.h" + +#include "../ddraw4/ddraw4_surface.h" + +#include "../d3d5/d3d5_viewport.h" +#include "../d3d3/d3d3_viewport.h" + +#include +#include + +namespace dxvk { + + uint32_t D3D6Viewport::s_viewportCount = 0; + + D3D6Viewport::D3D6Viewport( + D3DCommonViewport* commonViewport, + Com&& proxyViewport, + D3D6Interface* pParent) + : DDrawWrappedObject(pParent, std::move(proxyViewport), nullptr) + , m_commonViewport ( commonViewport ) { + + if (m_commonViewport == nullptr) + m_commonViewport = new D3DCommonViewport(m_parent->GetCommonD3DInterface()); + + m_commonViewport->SetD3D6Viewport(this); + + m_viewportCount = ++s_viewportCount; + + Logger::debug(str::format("D3D6Viewport: Created a new viewport nr. [[3-", m_viewportCount, "]]")); + } + + D3D6Viewport::~D3D6Viewport() { + std::vector>& lights = m_commonViewport->GetLights(); + + // Dissasociate every bound light from this viewport + for (auto light : lights) { + light->SetViewport6(nullptr); + } + + m_commonViewport->SetD3D6Viewport(nullptr); + + Logger::debug(str::format("D3D6Viewport: Viewport nr. [[3-", m_viewportCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> D3D6Viewport::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + // Some games query for legacy viewport interfaces + if (unlikely(riid == __uuidof(IDirect3DViewport))) { + if (m_commonViewport->GetD3D3Viewport() != nullptr) { + Logger::debug("D3D6Viewport::QueryInterface: Query for existing IDirect3DViewport"); + return m_commonViewport->GetD3D3Viewport()->QueryInterface(riid, ppvObject); + } + + Logger::debug("D3D6Viewport::QueryInterface: Query for IDirect3DViewport"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new D3D3Viewport(m_commonViewport.ptr(), std::move(ppvProxyObject), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirect3DViewport2))) { + if (m_commonViewport->GetD3D5Viewport() != nullptr) { + Logger::debug("D3D6Viewport::QueryInterface: Query for existing IDirect3DViewport2"); + return m_commonViewport->GetD3D5Viewport()->QueryInterface(riid, ppvObject); + } + + Logger::debug("D3D6Viewport::QueryInterface: Query for IDirect3DViewport2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new D3D5Viewport(m_commonViewport.ptr(), std::move(ppvProxyObject), nullptr)); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // Docs state: "The IDirect3DViewport3::Initialize method is not implemented." + HRESULT STDMETHODCALLTYPE D3D6Viewport::Initialize(IDirect3D *d3d) { + Logger::debug(">>> D3D6Viewport::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::GetViewport(D3DVIEWPORT *data) { + Logger::debug(">>> D3D6Viewport::GetViewport"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->IsViewportSet())) + return D3DERR_VIEWPORTDATANOTSET; + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + data->dwX = viewport9->X; + data->dwY = viewport9->Y; + data->dwWidth = viewport9->Width; + data->dwHeight = viewport9->Height; + data->dvMinZ = viewport9->MinZ; + data->dvMaxZ = viewport9->MaxZ; + + data->dvMaxX = 1.0f; + data->dvMaxY = 1.0f; + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + data->dvScaleX = legacyScale->x * (float)data->dwWidth / 2.0f; + data->dvScaleY = legacyScale->y * (float)data->dwHeight / 2.0f; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::SetViewport(D3DVIEWPORT *data) { + Logger::debug(">>> D3D6Viewport::SetViewport"); + + HRESULT hr = m_proxy->SetViewport(data); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + // TODO: Check viewport dimensions against the currently set RT, + // and perform some sanity checks (positive, non-zero dimensions) + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + // The docs state: "The method ignores the values in the dvMaxX, dvMaxY, + // dvMinZ, and dvMaxZ members.", which appears correct. + viewport9->X = data->dwX; + viewport9->Y = data->dwY; + viewport9->Width = data->dwWidth; + viewport9->Height = data->dwHeight; + viewport9->MinZ = 0.0f; + viewport9->MaxZ = 1.0f; + + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + legacyScale->x = 2.0f * data->dvScaleX / (float)data->dwWidth; + legacyScale->y = 2.0f * data->dvScaleY / (float)data->dwHeight; + legacyScale->z = 1.0f; + D3DVECTOR* legacyClip = m_commonViewport->GetLegacyClip(); + legacyClip->x = 0.0f; + legacyClip->y = 0.0f; + legacyClip->z = 0.0f; + + m_commonViewport->MarkViewportAsSet(); + + if (m_commonViewport->IsCurrentViewport()) + ApplyViewport(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::TransformVertices(DWORD vertex_count, D3DTRANSFORMDATA *data, DWORD flags, DWORD *offscreen) { + Logger::debug("<<< D3D6Viewport::TransformVertices: Proxy"); + return m_proxy->TransformVertices(vertex_count, data, flags, offscreen); + } + + // Docs state: "The IDirect3DViewport3::LightElements method is not currently implemented." + HRESULT STDMETHODCALLTYPE D3D6Viewport::LightElements(DWORD element_count, D3DLIGHTDATA *data) { + Logger::warn(">>> D3D6Viewport::LightElements"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::SetBackground(D3DMATERIALHANDLE hMat) { + Logger::debug(">>> D3D6Viewport::SetBackground"); + + if (unlikely(m_commonViewport->GetMaterialHandle() == hMat)) + return D3D_OK; + + D3DCommonMaterial* commonMaterial = m_commonViewport->GetCommonD3DInterface()->GetCommonMaterialFromHandle(hMat); + + if (unlikely(commonMaterial == nullptr)) + return DDERR_INVALIDPARAMS; + + m_commonViewport->MarkMaterialAsSet(); + + // Cache only the set material handle, as its color can + // change after it is set (get it on Clear directly) + m_commonViewport->SetMaterialHandle(hMat); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::GetBackground(D3DMATERIALHANDLE *material, BOOL *valid) { + Logger::debug(">>> D3D6Viewport::GetBackground"); + + if (unlikely(material == nullptr || valid == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(m_commonViewport->IsMaterialSet())) + *material = m_commonViewport->GetMaterialHandle(); + *valid = m_commonViewport->IsMaterialSet(); + + return D3D_OK; + } + + // One could speculate this was meant to set a z-buffer depth value + // to be used during clears, perhaps, similarly to SetBackground(), + // however it has not seen any practical use in the wild + HRESULT STDMETHODCALLTYPE D3D6Viewport::SetBackgroundDepth(IDirectDrawSurface *surface) { + Logger::warn("!!! D3D6Viewport::SetBackgroundDepth: Stub"); + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::GetBackgroundDepth(IDirectDrawSurface **surface, BOOL *valid) { + Logger::warn("!!! D3D6Viewport::SetBackgroundDepth: Stub"); + + if (unlikely(surface == nullptr || valid == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(surface); + + *valid = FALSE; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::Clear(DWORD count, D3DRECT *rects, DWORD flags) { + Logger::debug("<<< D3D6Viewport::Clear: Proxy"); + + // Fast skip + if (unlikely(!count && rects)) + return D3D_OK; + + HRESULT hr = m_proxy->Clear(count, rects, flags); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonViewport->GetD3D9Device(); + + // Temporarily activate this viewport in order to clear it + d3d9::D3DVIEWPORT9 currentViewport9; + if (!m_commonViewport->IsCurrentViewport()) { + D3D6Viewport* currentViewport = m_commonViewport->GetCurrentD3D6Viewport(); + if (currentViewport != nullptr) { + currentViewport9 = *currentViewport->GetCommonViewport()->GetD3D9Viewport(); + } else { + d3d9Device->GetViewport(¤tViewport9); + } + d3d9Device->SetViewport(m_commonViewport->GetD3D9Viewport()); + } + + static constexpr D3DCOLOR defaultColor = D3DCOLOR_RGBA(0, 0, 0, 0); + D3DMATERIALHANDLE handle = m_commonViewport->GetMaterialHandle(); + D3DCommonMaterial* commonMaterial = m_commonViewport->GetCommonD3DInterface()->GetCommonMaterialFromHandle(handle); + D3DCOLOR clearColor = commonMaterial != nullptr ? commonMaterial->GetMaterialColor() : defaultColor; + + HRESULT hr9 = d3d9Device->Clear(count, rects, flags, clearColor, 1.0f, 0u); + if (unlikely(FAILED(hr9))) + Logger::err("D3D6Viewport::Clear: Failed D3D9 Clear call"); + + // Restore the previously active viewport + if (!m_commonViewport->IsCurrentViewport()) { + d3d9Device->SetViewport(¤tViewport9); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::AddLight(IDirect3DLight *light) { + Logger::debug(">>> D3D6Viewport::AddLight"); + + if (unlikely(light == nullptr)) + return DDERR_INVALIDPARAMS; + + D3DLight* d3dLight = reinterpret_cast(light); + + if (unlikely(d3dLight->HasViewport())) + return D3DERR_LIGHTHASVIEWPORT; + + if (m_commonViewport->HasDevice()) { + Logger::debug("D3D6Viewport::AddLight: Enabling device legacy light model"); + m_commonViewport->EnableLegacyLights(d3dLight->IsD3DLight2()); + } + + std::vector>& lights = m_commonViewport->GetLights(); + // No need to check if the light is already attached, since + // if that's the case it will have a set viewport above + lights.push_back(d3dLight); + d3dLight->SetViewport6(this); + + if (m_commonViewport->HasDevice() && m_commonViewport->IsCurrentViewport()) + ApplyAndActivateLight(d3dLight->GetIndex(), d3dLight); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::DeleteLight(IDirect3DLight *light) { + Logger::debug(">>> D3D6Viewport::DeleteLight"); + + if (unlikely(light == nullptr)) + return DDERR_INVALIDPARAMS; + + D3DLight* d3dLight = reinterpret_cast(light); + + if (unlikely(!d3dLight->HasViewport())) + return DDERR_INVALIDPARAMS; + + std::vector>& lights = m_commonViewport->GetLights(); + + auto it = std::find(lights.begin(), lights.end(), d3dLight); + if (likely(it != lights.end())) { + const DWORD lightIndex = d3dLight->GetIndex(); + if (m_commonViewport->HasDevice() && m_commonViewport->IsCurrentViewport() && d3dLight->IsActive()) { + Logger::debug(str::format("D3D6Viewport: Disabling light nr. ", lightIndex)); + m_commonViewport->GetD3D9Device()->LightEnable(lightIndex, FALSE); + } + lights.erase(it); + d3dLight->SetViewport6(nullptr); + } else { + Logger::warn("D3D6Viewport::DeleteLight: Light not found"); + return DDERR_INVALIDPARAMS; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::NextLight(IDirect3DLight *lpDirect3DLight, IDirect3DLight **lplpDirect3DLight, DWORD flags) { + Logger::debug(">>> D3D6Viewport::NextLight"); + + if (unlikely(lplpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDirect3DLight); + + std::vector>& lights = m_commonViewport->GetLights(); + + if (flags & D3DNEXT_HEAD) { + if (likely(lights.size() > 0)) + *lplpDirect3DLight = lights.front().ref(); + } else if (flags & D3DNEXT_NEXT) { + if (unlikely(lpDirect3DLight == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(lights.size() > 0)) + Logger::warn("D3D6Viewport::NextLight: Unimplemented D3DNEXT_NEXT flag"); + } else if (flags & D3DNEXT_TAIL) { + if (likely(lights.size() > 0)) + *lplpDirect3DLight = lights.back().ref(); + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::GetViewport2(D3DVIEWPORT2 *data) { + Logger::debug(">>> D3D6Viewport::GetViewport2"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT2))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->IsViewportSet())) + return D3DERR_VIEWPORTDATANOTSET; + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + data->dwX = viewport9->X; + data->dwY = viewport9->Y; + data->dwWidth = viewport9->Width; + data->dwHeight = viewport9->Height; + data->dvMinZ = viewport9->MinZ; + data->dvMaxZ = viewport9->MaxZ; + + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + data->dvClipWidth = 2.0f / legacyScale->x; + data->dvClipHeight = 2.0f / legacyScale->y; + D3DVECTOR* legacyClip = m_commonViewport->GetLegacyClip(); + data->dvClipX = data->dvClipWidth * (legacyClip->x + 1.0f) / -2.0f; + data->dvClipY = data->dvClipHeight * (legacyClip->y - 1.0f) / -2.0f; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::SetViewport2(D3DVIEWPORT2 *data) { + Logger::debug(">>> D3D6Viewport::SetViewport2"); + + HRESULT hr = m_proxy->SetViewport2(data); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(data->dwSize != sizeof(D3DVIEWPORT2))) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + // TODO: Check viewport dimensions against the currently set RT, + // and perform some sanity checks (positive, non-zero dimensions) + + d3d9::D3DVIEWPORT9* viewport9 = m_commonViewport->GetD3D9Viewport(); + + viewport9->X = data->dwX; + viewport9->Y = data->dwY; + viewport9->Width = data->dwWidth; + viewport9->Height = data->dwHeight; + viewport9->MinZ = 0.0f; + viewport9->MaxZ = 1.0f; + + D3DVECTOR* legacyScale = m_commonViewport->GetLegacyScale(); + legacyScale->x = 2.0f / data->dvClipWidth; + legacyScale->y = 2.0f / data->dvClipHeight; + legacyScale->z = 1.0f / (data->dvMaxZ - data->dvMinZ); + D3DVECTOR* legacyClip = m_commonViewport->GetLegacyClip(); + legacyClip->x = -2.0f * data->dvClipX / data->dvClipWidth - 1.0f; + legacyClip->y = -2.0f * data->dvClipY / data->dvClipHeight + 1.0f; + legacyClip->z = -data->dvMinZ / (data->dvMaxZ - data->dvMinZ); + + m_commonViewport->MarkViewportAsSet(); + + if (m_commonViewport->IsCurrentViewport()) + ApplyViewport(); + + return D3D_OK; + } + + // One could speculate this was meant to set a z-buffer depth value + // to be used during clears, perhaps, similarly to SetBackground(), + // however it has not seen any practical use in the wild + HRESULT STDMETHODCALLTYPE D3D6Viewport::SetBackgroundDepth2(IDirectDrawSurface4 *surface) { + Logger::warn("!!! D3D6Viewport::SetBackgroundDepth2: Stub"); + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::GetBackgroundDepth2(IDirectDrawSurface4 **surface, BOOL *valid) { + Logger::warn("!!! D3D6Viewport::GetBackgroundDepth2: Stub"); + + if (unlikely(surface == nullptr || valid == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(surface); + + *valid = FALSE; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D6Viewport::Clear2(DWORD count, D3DRECT *rects, DWORD flags, DWORD color, D3DVALUE z, DWORD stencil) { + Logger::debug("<<< D3D6Viewport::Clear2: Proxy"); + + // Fast skip + if (unlikely(!count && rects)) + return D3D_OK; + + HRESULT hr = m_proxy->Clear2(count, rects, flags, color, z, stencil); + if (unlikely(FAILED(hr))) + return hr; + + if (unlikely(!m_commonViewport->HasDevice())) + return D3DERR_VIEWPORTHASNODEVICE; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonViewport->GetD3D9Device(); + + // Temporarily activate this viewport in order to clear it + d3d9::D3DVIEWPORT9 currentViewport9; + if (!m_commonViewport->IsCurrentViewport()) { + D3D6Viewport* currentViewport = m_commonViewport->GetCurrentD3D6Viewport(); + if (currentViewport != nullptr) { + currentViewport9 = *currentViewport->GetCommonViewport()->GetD3D9Viewport(); + } else { + d3d9Device->GetViewport(¤tViewport9); + } + d3d9Device->SetViewport(m_commonViewport->GetD3D9Viewport()); + } + + HRESULT hr9 = d3d9Device->Clear(count, rects, flags, color, z, stencil); + + // Restore the previously active viewport + if (!m_commonViewport->IsCurrentViewport()) { + d3d9Device->SetViewport(¤tViewport9); + } + + if (unlikely(FAILED(hr9))) { + Logger::err("D3D6Viewport::Clear2: Failed D3D9 Clear call"); + // Unlike Clear(), Clear2() is supposed to error out on + // z/stencil clears and no z/stencil attachments + return hr; + } + + return D3D_OK; + } + + HRESULT D3D6Viewport::ApplyViewport() { + if (!m_commonViewport->IsViewportSet()) + return D3D_OK; + + Logger::debug("D3D6Viewport: Applying viewport to D3D9"); + + HRESULT hr = m_commonViewport->GetD3D9Device()->SetViewport(m_commonViewport->GetD3D9Viewport()); + if(unlikely(FAILED(hr))) + Logger::err("D3D6Viewport: Failed to set the D3D9 viewport"); + + return hr; + } + + HRESULT D3D6Viewport::ApplyAndActivateLights() { + std::vector>& lights = m_commonViewport->GetLights(); + + if (!lights.size()) + return D3D_OK; + + Logger::debug("D3D6Viewport: Applying lights to D3D9"); + + for (auto light: lights) + ApplyAndActivateLight(light->GetIndex(), light.ptr()); + + return D3D_OK; + } + + HRESULT D3D6Viewport::ApplyAndActivateLight(DWORD index, D3DLight* light) { + d3d9::IDirect3DDevice9* d3d9Device = m_commonViewport->GetD3D9Device(); + + HRESULT hr = d3d9Device->SetLight(index, light->GetD3D9Light()); + if (unlikely(FAILED(hr))) { + Logger::err("D3D6Viewport: Failed D3D9 SetLight call"); + } else { + HRESULT hrLE; + if (light->IsActive()) { + Logger::debug(str::format("D3D6Viewport: Enabling light nr. ", index)); + hrLE = d3d9Device->LightEnable(index, TRUE); + if (unlikely(FAILED(hrLE))) + Logger::err("D3D6Viewport: Failed D3D9 LightEnable call (TRUE)"); + } else { + Logger::debug(str::format("D3D6Viewport: Disabling light nr. ", index)); + hrLE = d3d9Device->LightEnable(index, FALSE); + if (unlikely(FAILED(hrLE))) + Logger::err("D3D6Viewport: Failed D3D9 LightEnable call (FALSE)"); + } + } + + return hr; + } + +} diff --git a/src/ddraw/d3d6/d3d6_viewport.h b/src/ddraw/d3d6/d3d6_viewport.h new file mode 100644 index 00000000000..750549cb9ff --- /dev/null +++ b/src/ddraw/d3d6/d3d6_viewport.h @@ -0,0 +1,83 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_util.h" + +#include "../d3d_common_viewport.h" + +#include "d3d6_interface.h" + +namespace dxvk { + + class D3DLight; + + class D3D6Viewport final : public DDrawWrappedObject { + + public: + + D3D6Viewport( + D3DCommonViewport* commonViewport, + Com&& proxyViewport, + D3D6Interface* pParent); + + ~D3D6Viewport(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE Initialize(IDirect3D *d3d); + + HRESULT STDMETHODCALLTYPE GetViewport(D3DVIEWPORT *data); + + HRESULT STDMETHODCALLTYPE SetViewport(D3DVIEWPORT *data); + + HRESULT STDMETHODCALLTYPE TransformVertices(DWORD vertex_count, D3DTRANSFORMDATA *data, DWORD flags, DWORD *offscreen); + + HRESULT STDMETHODCALLTYPE LightElements(DWORD element_count, D3DLIGHTDATA *data); + + HRESULT STDMETHODCALLTYPE SetBackground(D3DMATERIALHANDLE hMat); + + HRESULT STDMETHODCALLTYPE GetBackground(D3DMATERIALHANDLE *material, BOOL *valid); + + HRESULT STDMETHODCALLTYPE SetBackgroundDepth(IDirectDrawSurface *surface); + + HRESULT STDMETHODCALLTYPE GetBackgroundDepth(IDirectDrawSurface **surface, BOOL *valid); + + HRESULT STDMETHODCALLTYPE Clear(DWORD count, D3DRECT *rects, DWORD flags); + + HRESULT STDMETHODCALLTYPE AddLight(IDirect3DLight *light); + + HRESULT STDMETHODCALLTYPE DeleteLight(IDirect3DLight *light); + + HRESULT STDMETHODCALLTYPE NextLight(IDirect3DLight *lpDirect3DLight, IDirect3DLight **lplpDirect3DLight, DWORD flags); + + HRESULT STDMETHODCALLTYPE GetViewport2(D3DVIEWPORT2 *data); + + HRESULT STDMETHODCALLTYPE SetViewport2(D3DVIEWPORT2 *data); + + HRESULT STDMETHODCALLTYPE SetBackgroundDepth2(IDirectDrawSurface4 *surface); + + HRESULT STDMETHODCALLTYPE GetBackgroundDepth2(IDirectDrawSurface4 **surface, BOOL *valid); + + HRESULT STDMETHODCALLTYPE Clear2(DWORD count, D3DRECT *rects, DWORD flags, DWORD color, D3DVALUE z, DWORD stencil); + + HRESULT ApplyViewport(); + + HRESULT ApplyAndActivateLights(); + + HRESULT ApplyAndActivateLight(DWORD index, D3DLight* light); + + D3DCommonViewport* GetCommonViewport() const { + return m_commonViewport.ptr(); + } + + private: + + static uint32_t s_viewportCount; + uint32_t m_viewportCount = 0; + + Com m_commonViewport; + + }; + +} diff --git a/src/ddraw/d3d7/d3d7_buffer.cpp b/src/ddraw/d3d7/d3d7_buffer.cpp new file mode 100644 index 00000000000..70ae307852c --- /dev/null +++ b/src/ddraw/d3d7/d3d7_buffer.cpp @@ -0,0 +1,233 @@ +#include "d3d7_buffer.h" + +#include "../ddraw_util.h" + +#include "../d3d_multithread.h" + +#include "../ddraw7/ddraw7_interface.h" + +namespace dxvk { + + uint32_t D3D7VertexBuffer::s_buffCount = 0; + + D3D7VertexBuffer::D3D7VertexBuffer( + Com&& buffProxy, + Com&& pBuffer9, + D3D7Interface* pParent, + D3DVERTEXBUFFERDESC desc) + : DDrawWrappedObject(pParent, std::move(buffProxy), std::move(pBuffer9)) + , m_commonIntf ( pParent->GetCommonInterface() ) + , m_desc ( desc ) + , m_stride ( GetFVFSize(desc.dwFVF) ) + , m_size ( m_stride * desc.dwNumVertices ) { + m_parent->AddRef(); + + m_buffCount = ++s_buffCount; + + ListBufferDetails(); + } + + D3D7VertexBuffer::~D3D7VertexBuffer() { + m_parent->Release(); + + Logger::debug(str::format("D3D7VertexBuffer: Buffer nr. {{7-", m_buffCount, "}} bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D7VertexBuffer::GetVertexBufferDesc(LPD3DVERTEXBUFFERDESC lpVBDesc) { + Logger::debug(">>> D3D7VertexBuffer::GetVertexBufferDesc"); + + if (unlikely(lpVBDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + const DWORD dwSize = lpVBDesc->dwSize; + + *lpVBDesc = m_desc; + // The value passed in dwSize during the query is expected to be + // preserved, even if it is not equal to sizeof(D3DVERTEXBUFFERDESC) + lpVBDesc->dwSize = dwSize; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7VertexBuffer::Lock(DWORD flags, void **data, DWORD *data_size) { + Logger::debug(">>> D3D7VertexBuffer::Lock"); + + if (unlikely(IsOptimized())) + return D3DERR_VERTEXBUFFEROPTIMIZED; + + RefreshD3D7Device(); + if (unlikely(!IsInitialized())) { + HRESULT hrInit = InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + if (data_size != nullptr) + *data_size = m_size; + + HRESULT hr = m_d3d9->Lock(0, 0, data, ConvertD3D7LockFlags(flags, m_legacyDiscard, false)); + + if (likely(SUCCEEDED(hr))) + m_locked = true; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7VertexBuffer::Unlock() { + Logger::debug(">>> D3D7VertexBuffer::Unlock"); + + RefreshD3D7Device(); + if (unlikely(!IsInitialized())) { + HRESULT hrInit = InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + HRESULT hr = m_d3d9->Unlock(); + + if (likely(SUCCEEDED(hr))) + m_locked = false; + else + return D3DERR_VERTEXBUFFERUNLOCKFAILED; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7VertexBuffer::ProcessVertices(DWORD dwVertexOp, DWORD dwDestIndex, DWORD dwCount, LPDIRECT3DVERTEXBUFFER7 lpSrcBuffer, DWORD dwSrcIndex, LPDIRECT3DDEVICE7 lpD3DDevice, DWORD dwFlags) { + Logger::debug(">>> D3D7VertexBuffer::ProcessVertices"); + + if (unlikely(!dwCount)) + return D3D_OK; + + if (unlikely(lpD3DDevice == nullptr || lpSrcBuffer == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!(dwVertexOp & D3DVOP_TRANSFORM))) + return DDERR_INVALIDPARAMS; + + D3D7Device* device = static_cast(lpD3DDevice); + D3D7VertexBuffer* vb = static_cast(lpSrcBuffer); + + vb->RefreshD3D7Device(); + if (unlikely(vb->GetDevice() == nullptr || device != vb->GetDevice())) { + Logger::err("D3D7VertexBuffer::ProcessVertices: Incompatible or null device"); + return DDERR_GENERIC; + } + + HRESULT hrInit; + + // Check and initialize the source buffer + if (unlikely(!vb->IsInitialized())) { + hrInit = vb->InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + // Check and initialize the destination buffer (this buffer) + RefreshD3D7Device(); + if (unlikely(!IsInitialized())) { + hrInit = InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + D3DDeviceLock lock = device->LockDevice(); + + HandlePreProcessVerticesFlags(dwVertexOp); + + device->GetD3D9()->SetFVF(m_desc.dwFVF); + device->GetD3D9()->SetStreamSource(0, vb->GetD3D9(), 0, vb->GetStride()); + HRESULT hr = device->GetD3D9()->ProcessVertices(dwSrcIndex, dwDestIndex, dwCount, m_d3d9.ptr(), nullptr, dwFlags); + if (unlikely(FAILED(hr))) { + Logger::err("D3D7VertexBuffer::ProcessVertices: Failed call to D3D9 ProcessVertices"); + } + + HandlePostProcessVerticesFlags(dwVertexOp); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7VertexBuffer::ProcessVerticesStrided(DWORD dwVertexOp, DWORD dwDestIndex, DWORD dwCount, LPD3DDRAWPRIMITIVESTRIDEDDATA lpVertexArray, DWORD dwSrcIndex, LPDIRECT3DDEVICE7 lpD3DDevice, DWORD dwFlags) { + Logger::warn("!!! D3D7VertexBuffer::ProcessVerticesStrided: Stub"); + + if (unlikely(!dwCount)) + return D3D_OK; + + if(unlikely(lpD3DDevice == nullptr)) + return DDERR_INVALIDPARAMS; + + D3D7Device* device = static_cast(lpD3DDevice); + + RefreshD3D7Device(); + if(unlikely(m_d3d7Device == nullptr || device != m_d3d7Device)) { + Logger::err(">>> D3D7VertexBuffer::ProcessVerticesStrided: Incompatible or null device"); + return DDERR_GENERIC; + } + + // Check and initialize the destination buffer (this buffer) + if (unlikely(!IsInitialized())) { + HRESULT hrInit = InitializeD3D9(); + if (unlikely(FAILED(hrInit))) + return hrInit; + } + + D3DDeviceLock lock = device->LockDevice(); + + HandlePreProcessVerticesFlags(dwVertexOp); + + // TODO: lpVertexArray needs to be transformed into a non-strided vertex buffer stream + + HandlePostProcessVerticesFlags(dwVertexOp); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7VertexBuffer::Optimize(LPDIRECT3DDEVICE7 lpD3DDevice, DWORD dwFlags) { + Logger::debug(">>> D3D7VertexBuffer::Optimize"); + + if (unlikely(lpD3DDevice == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(IsLocked())) + return D3DERR_VERTEXBUFFERLOCKED; + + if (unlikely(IsOptimized())) + return D3DERR_VERTEXBUFFEROPTIMIZED; + + m_desc.dwCaps &= D3DVBCAPS_OPTIMIZED; + + return D3D_OK; + }; + + HRESULT D3D7VertexBuffer::InitializeD3D9() { + // Can't create anything without a valid device + if (unlikely(m_d3d7Device == nullptr)) { + Logger::warn("D3D7VertexBuffer::InitializeD3D9: Null D3D7 device, can't initialize right now"); + return DDERR_GENERIC; + } + + d3d9::D3DPOOL pool = d3d9::D3DPOOL_DEFAULT; + + if (m_desc.dwCaps & D3DVBCAPS_SYSTEMMEMORY) + pool = d3d9::D3DPOOL_SYSTEMMEM; + + const char* poolPlacement = pool == d3d9::D3DPOOL_DEFAULT ? "D3DPOOL_DEFAULT" : "D3DPOOL_SYSTEMMEM"; + + Logger::debug(str::format("D3D7VertexBuffer::InitializeD3D9: Placing in: ", poolPlacement)); + + const DWORD usage = ConvertD3D7UsageFlags(m_desc.dwCaps); + m_legacyDiscard = m_commonIntf->GetOptions()->forceLegacyDiscard && + (usage & D3DUSAGE_DYNAMIC) && (usage & D3DUSAGE_WRITEONLY); + HRESULT hr = m_d3d7Device->GetD3D9()->CreateVertexBuffer(m_size, usage, m_desc.dwFVF, pool, &m_d3d9, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D7VertexBuffer::InitializeD3D9: Failed to create D3D9 vertex buffer"); + return hr; + } + + Logger::debug("D3D7VertexBuffer::InitializeD3D9: Created D3D9 vertex buffer"); + + return DD_OK; + } + +} diff --git a/src/ddraw/d3d7/d3d7_buffer.h b/src/ddraw/d3d7/d3d7_buffer.h new file mode 100644 index 00000000000..24a78145b65 --- /dev/null +++ b/src/ddraw/d3d7/d3d7_buffer.h @@ -0,0 +1,114 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../ddraw_common_interface.h" + +#include "d3d7_interface.h" +#include "d3d7_device.h" + +namespace dxvk { + + class D3D7VertexBuffer final : public DDrawWrappedObject { + + public: + + D3D7VertexBuffer( + Com&& buffProxy, + Com&& pBuffer9, + D3D7Interface* pParent, + D3DVERTEXBUFFERDESC desc); + + ~D3D7VertexBuffer(); + + HRESULT STDMETHODCALLTYPE GetVertexBufferDesc(LPD3DVERTEXBUFFERDESC lpVBDesc); + + HRESULT STDMETHODCALLTYPE Lock(DWORD flags, void **data, DWORD *data_size); + + HRESULT STDMETHODCALLTYPE Unlock(); + + HRESULT STDMETHODCALLTYPE ProcessVertices(DWORD dwVertexOp, DWORD dwDestIndex, DWORD dwCount, LPDIRECT3DVERTEXBUFFER7 lpSrcBuffer, DWORD dwSrcIndex, LPDIRECT3DDEVICE7 lpD3DDevice, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE ProcessVerticesStrided(DWORD dwVertexOp, DWORD dwDestIndex, DWORD dwCount, LPD3DDRAWPRIMITIVESTRIDEDDATA lpVertexArray, DWORD dwSrcIndex, LPDIRECT3DDEVICE7 lpD3DDevice, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE Optimize(LPDIRECT3DDEVICE7 lpD3DDevice, DWORD dwFlags); + + DWORD GetFVF() const { + return m_desc.dwFVF; + } + + DWORD GetStride() const { + return m_stride; + } + + bool IsLocked() const { + return m_locked; + } + + D3D7Device* GetDevice() const { + return m_d3d7Device; + } + + void RefreshD3D7Device() { + D3D7Device* d3d7Device = m_commonIntf->GetD3D7Device(); + if (unlikely(m_d3d7Device != d3d7Device)) { + // Check if the device has been recreated and reset all D3D9 resources + if (unlikely(m_d3d7Device != nullptr)) { + Logger::debug("D3D7VertexBuffer::RefreshD3D7Device: Device context has changed, clearing D3D9 buffers"); + m_d3d9 = nullptr; + } + m_d3d7Device = d3d7Device; + } + } + + HRESULT InitializeD3D9(); + + private: + + inline bool IsOptimized() const { + return m_desc.dwCaps & D3DVBCAPS_OPTIMIZED; + } + + inline void HandlePreProcessVerticesFlags(DWORD pvFlags) { + // Disable lighting if the D3DVOP_LIGHT isn't specified + if (!(pvFlags & D3DVOP_LIGHT)) { + m_d3d7Device->GetD3D9()->GetRenderState(d3d9::D3DRS_LIGHTING, &m_lighting); + if (m_lighting) + m_d3d7Device->GetD3D9()->SetRenderState(d3d9::D3DRS_LIGHTING, FALSE); + } + } + + inline void HandlePostProcessVerticesFlags(DWORD pvFlags) { + if (!(pvFlags & D3DVOP_LIGHT) && m_lighting) { + m_d3d7Device->GetD3D9()->SetRenderState(d3d9::D3DRS_LIGHTING, TRUE); + } + } + + inline void ListBufferDetails() const { + Logger::debug(str::format("D3D7VertexBuffer: Created a new buffer nr. {{7-", m_buffCount, "}}:")); + Logger::debug(str::format(" Size: ", m_size)); + Logger::debug(str::format(" FVF: ", m_desc.dwFVF)); + Logger::debug(str::format(" Vertices: ", m_size / m_stride)); + } + + bool m_locked = false; + bool m_legacyDiscard = false; + + static uint32_t s_buffCount; + uint32_t m_buffCount = 0; + + DDrawCommonInterface* m_commonIntf = nullptr; + + D3D7Device* m_d3d7Device = nullptr; + + DWORD m_lighting = FALSE; + + D3DVERTEXBUFFERDESC m_desc; + + UINT m_stride = 0; + UINT m_size = 0; + + }; + +} diff --git a/src/ddraw/d3d7/d3d7_device.cpp b/src/ddraw/d3d7/d3d7_device.cpp new file mode 100644 index 00000000000..68baadc0f80 --- /dev/null +++ b/src/ddraw/d3d7/d3d7_device.cpp @@ -0,0 +1,1493 @@ +#include "d3d7_device.h" + +#include "d3d7_buffer.h" +#include "d3d7_state_block.h" +#include + +#include "../ddraw7/ddraw7_surface.h" + +namespace dxvk { + + uint32_t D3D7Device::s_deviceCount = 0; + + D3D7Device::D3D7Device( + Com&& d3d7DeviceProxy, + D3D7Interface* pParent, + D3DDEVICEDESC7 Desc, + d3d9::D3DPRESENT_PARAMETERS Params9, + Com&& pDevice9, + DDraw7Surface* pSurface, + DWORD CreationFlags9) + : DDrawWrappedObject(pParent, std::move(d3d7DeviceProxy), std::move(pDevice9)) + , m_commonIntf ( pParent->GetCommonInterface() ) + , m_multithread ( CreationFlags9 & D3DCREATE_MULTITHREADED ) + , m_params9 ( Params9 ) + , m_desc ( Desc ) + , m_rt ( pSurface ) { + // Get the bridge interface to D3D9 + if (unlikely(FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8Bridge), reinterpret_cast(&m_bridge))))) { + throw DxvkError("D3D7Device: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + // Common D3D9 index buffers + if (unlikely(FAILED(InitializeIndexBuffers()))) { + throw DxvkError("D3D7Device: ERROR! Failed to initialize D3D9 index buffers."); + } + + m_totalMemory = m_bridge->DetermineInitialTextureMemory(); + + m_textures.fill(nullptr); + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->emulateFSAA == FSAAEmulation::Forced)) { + Logger::warn("D3D7Device: Force enabling AA"); + m_d3d9->SetRenderState(d3d9::D3DRS_MULTISAMPLEANTIALIAS, TRUE); + } + + m_deviceCount = ++s_deviceCount; + + Logger::debug(str::format("D3D7Device: Created a new device nr. ((7-", m_deviceCount, "))")); + } + + D3D7Device::~D3D7Device() { + if (LogIndexBufferUsageStats()) { + Logger::info("D3D7Device: Index buffer upload statistics:"); + Logger::info(str::format(" XXS: ", m_ib9_uploads[0])); + Logger::info(str::format(" XS : ", m_ib9_uploads[1])); + Logger::info(str::format(" S : ", m_ib9_uploads[2])); + Logger::info(str::format(" M : ", m_ib9_uploads[3])); + Logger::info(str::format(" L : ", m_ib9_uploads[4])); + Logger::info(str::format(" XL : ", m_ib9_uploads[5])); + Logger::info(str::format(" XXL: ", m_ib9_uploads[6])); + } + + // Clear the common interface device pointer if it points to this device + if (m_commonIntf->GetD3D7Device() == this) + m_commonIntf->SetD3D7Device(nullptr); + + Logger::debug(str::format("D3D7Device: Device nr. ((7-", m_deviceCount, ")) bites the dust")); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetCaps(D3DDEVICEDESC7 *desc) { + Logger::debug(">>> D3D7Device::GetCaps"); + + if (unlikely(desc == nullptr)) + return DDERR_INVALIDPARAMS; + + *desc = m_desc; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::EnumTextureFormats(LPD3DENUMPIXELFORMATSCALLBACK cb, void *ctx) { + Logger::debug(">>> D3D7Device::EnumTextureFormats"); + + if (unlikely(cb == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // Note: The list of formats exposed in D3D7 is restricted to the below + + DDPIXELFORMAT textureFormat = GetTextureFormat(d3d9::D3DFMT_X1R5G5B5); + HRESULT hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_A1R5G5B5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + // D3DFMT_X4R4G4B4 is not supported by D3D7 + textureFormat = GetTextureFormat(d3d9::D3DFMT_A4R4G4B4); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_R5G6B5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_X8R8G8B8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_A8R8G8B8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + // Not supported in D3D9, but some games need + // it to be advertised (for offscreen plain surfaces?) + if (unlikely(d3dOptions->supportR3G3B2)) { + textureFormat = GetTextureFormat(d3d9::D3DFMT_R3G3B2); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + } + + // Not supported in D3D9, but some games may use it + // Note: Advertizing P8 support breaks Sacrifice + /*textureFormat = GetTextureFormat(d3d9::D3DFMT_P8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK;*/ + + textureFormat = GetTextureFormat(d3d9::D3DFMT_V8U8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_L6V5U5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_X8L8V8U8); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT1); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT2); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT3); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT4); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + textureFormat = GetTextureFormat(d3d9::D3DFMT_DXT5); + hr = cb(&textureFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::BeginScene() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::BeginScene"); + + RefreshLastUsedDevice(); + + if (unlikely(m_inScene)) + return D3DERR_SCENE_IN_SCENE; + + HRESULT hr = m_d3d9->BeginScene(); + + if (likely(SUCCEEDED(hr))) + m_inScene = true; + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::EndScene() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::EndScene"); + + RefreshLastUsedDevice(); + + if (unlikely(!m_inScene)) + return D3DERR_SCENE_NOT_IN_SCENE; + + HRESULT hr = m_d3d9->EndScene(); + + if (likely(SUCCEEDED(hr))) { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (d3dOptions->forceProxiedPresent) { + // If we have drawn anything, we need to make sure we blit back + // the results onto the D3D7 render target before we flip it + if (m_commonIntf->HasDrawn()) + BlitToDDrawSurface(m_rt->GetProxied(), m_rt->GetD3D9()); + + m_rt->GetProxied()->Flip(static_cast(m_commonIntf->GetFlipRTSurface()), + m_commonIntf->GetFlipRTFlags()); + + if (likely(d3dOptions->backBufferGuard != D3DBackBufferGuard::Strict)) + m_commonIntf->ResetDrawTracking(); + } + + m_inScene = false; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetDirect3D(IDirect3D7 **d3d) { + Logger::debug(">>> D3D7Device::GetDirect3D"); + + if (unlikely(d3d == nullptr)) + return DDERR_INVALIDPARAMS; + + *d3d = ref(m_parent); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetRenderTarget(IDirectDrawSurface7 *surface, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::SetRenderTarget"); + + if (unlikely(surface == nullptr)) { + Logger::err("D3D7Device::SetRenderTarget: NULL render target"); + return DDERR_INVALIDPARAMS; + } + + if (unlikely(!m_commonIntf->IsWrappedSurface(surface))) { + Logger::err("D3D7Device::SetRenderTarget: Received an unwrapped RT"); + return DDERR_GENERIC; + } + + DDraw7Surface* rt7 = static_cast(surface); + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->forceProxiedPresent)) { + HRESULT hrRT = m_proxy->SetRenderTarget(rt7->GetProxied(), flags); + if (unlikely(FAILED(hrRT))) { + Logger::warn("D3D7Device::SetRenderTarget: Failed to set RT"); + return hrRT; + } + } else { + // Needed to ensure proxied Z/Stencil viewport clears will work + HRESULT hrRT = m_proxy->SetRenderTarget(rt7->GetProxied(), flags); + if (unlikely(FAILED(hrRT))) + Logger::debug("D3D7Device::SetRenderTarget: Failed to set RT"); + } + + HRESULT hr = rt7->GetCommonSurface()->ValidateRTUsage(); + if (unlikely(FAILED(hr))) + return hr; + + hr = rt7->InitializeD3D9RenderTarget(); + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::SetRenderTarget: Failed to initialize D3D9 RT"); + return hr; + } + + hr = m_d3d9->SetRenderTarget(0, rt7->GetD3D9()); + + // TODO: Test suggest that the passed surface is saved as the + // current RT even if the call fails, or it is an invalid surface... + // The current viewport values however must remain unaffected. + + if (likely(SUCCEEDED(hr))) { + Logger::debug("D3D7Device::SetRenderTarget: Set a new D3D9 RT"); + + m_rt = rt7; + m_ds = m_rt->GetAttachedDepthStencil(); + + HRESULT hrDS; + + if (m_ds != nullptr) { + Logger::debug("D3D7Device::SetRenderTarget: Found an attached DS"); + + hrDS = m_ds->InitializeD3D9DepthStencil(); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D7Device::SetRenderTarget: Failed to initialize/upload D3D9 DS"); + return hrDS; + } + + hrDS = m_d3d9->SetDepthStencilSurface(m_ds->GetD3D9()); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D7Device::SetRenderTarget: Failed to set D3D9 DS"); + return hrDS; + } + + Logger::debug("D3D7Device::SetRenderTarget: Set a new D3D9 DS"); + } else { + Logger::debug("D3D7Device::SetRenderTarget: RT has no depth stencil attached"); + + hrDS = m_d3d9->SetDepthStencilSurface(nullptr); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D7Device::SetRenderTarget: Failed to clear the D3D9 DS"); + return hrDS; + } + + Logger::debug("D3D7Device::SetRenderTarget: Cleared the D3D9 DS"); + } + } else { + Logger::err("D3D7Device::SetRenderTarget: Failed to set D3D9 RT"); + return hr; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetRenderTarget(IDirectDrawSurface7 **surface) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::GetRenderTarget"); + + if (unlikely(surface == nullptr)) + return DDERR_INVALIDPARAMS; + + *surface = m_rt.ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::Clear(DWORD count, D3DRECT *rects, DWORD flags, D3DCOLOR color, D3DVALUE z, DWORD stencil) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::Clear"); + + // We are now allowing proxy back buffer blits in certain cases, so + // we must also ensure the back buffer clear calls are proxied + HRESULT hr = m_proxy->Clear(count, rects, flags, color, z, stencil); + if (unlikely(FAILED(hr))) + Logger::debug("D3D7Device::Clear: Failed proxied call"); + + return m_d3d9->Clear(count, rects, flags, color, static_cast(z), stencil); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D7Device::SetTransform"); + return m_d3d9->SetTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D7Device::GetTransform"); + return m_d3d9->GetTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::MultiplyTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix) { + Logger::debug(">>> D3D7Device::MultiplyTransform"); + return m_d3d9->MultiplyTransform(ConvertTransformState(state), matrix); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetViewport(D3DVIEWPORT7 *data) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::SetViewport"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + // Clear() calls are affected by the set viewport, so we + // must ensure SetViewport() calls are also proxied + HRESULT hr = m_proxy->SetViewport(data); + if (unlikely(FAILED(hr))) + Logger::debug("D3D7Device::SetViewport: Failed proxied call"); + + const DDSURFACEDESC2* rtDesc2 = m_rt->GetCommonSurface()->GetDesc2(); + + // D3D7 will fail when setting a viewport that's outside of the + // current render target, though that works in D3D9 + if (unlikely(data->dwX + data->dwWidth > rtDesc2->dwWidth || + data->dwY + data->dwHeight > rtDesc2->dwHeight)) { + // On Linux/Wine and in windowed mode, we can get in situations + // where the actual render target dimensions are off by one + // pixel to what the game sets them to. Allow this corner case + // to skip the validation, in order to prevent issues. + const bool isOnePixelWider = data->dwX + data->dwWidth == rtDesc2->dwWidth + 1; + const bool isOnePixelTaller = data->dwY + data->dwHeight == rtDesc2->dwHeight + 1; + + if (unlikely(isOnePixelWider || isOnePixelTaller)) { + Logger::debug("D3D7Device::SetViewport: Viewport exceeds render target dimensions by one pixel"); + } else { + Logger::debug("D3D7Device::SetViewport: Viewport exceeds render target dimensions"); + return DDERR_INVALIDPARAMS; + } + } + + // (The) Summoner sets both to 0.0f and expects to get + // the behavioral equivalent of setting 0.0f/1.0f, although + // the actual D3D7 behavior will result in 0.0f/0.001f. + // + // It is somewhat unclear why this works properly on native, + // however it is possible some corrections were performed at + // driver level or in the runtime, but without affecting + // reported viewport dvMinZ/dvMaxZ values. + if (unlikely(data->dvMinZ == 0.0f && data->dvMaxZ == 0.0f)) + data->dvMaxZ = 1.0f; + + return m_d3d9->SetViewport(reinterpret_cast(data)); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetViewport(D3DVIEWPORT7 *data) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::GetViewport"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + return m_d3d9->GetViewport(reinterpret_cast(data)); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetMaterial(D3DMATERIAL7 *data) { + Logger::debug(">>> D3D7Device::SetMaterial"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + return m_d3d9->SetMaterial(reinterpret_cast(data)); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetMaterial(D3DMATERIAL7 *data) { + Logger::debug(">>> D3D7Device::GetMaterial"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + return m_d3d9->GetMaterial(reinterpret_cast(data)); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetLight(DWORD idx, D3DLIGHT7 *data) { + Logger::debug(">>> D3D7Device::SetLight"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + // D3DLIGHT_PARALLELPOINT can not be used in D3D7 + if (unlikely(data->dltType != D3DLIGHT_POINT + && data->dltType != D3DLIGHT_SPOT + && data->dltType != D3DLIGHT_DIRECTIONAL)) + return DDERR_INVALIDPARAMS; + + // For POINT/SPOT lights, attenuation should be positive, as per docs: + // "Valid values for these members range from 0.0 to infinity." + if (unlikely((data->dltType == D3DLIGHT_POINT + || data->dltType == D3DLIGHT_SPOT) && + (data->dvAttenuation0 < 0.0f + || data->dvAttenuation1 < 0.0f + || data->dvAttenuation2 < 0.0f))) + return DDERR_INVALIDPARAMS; + + HRESULT hr = m_d3d9->SetLight(idx, reinterpret_cast(data)); + if (unlikely(FAILED(hr))) + return DDERR_INVALIDPARAMS; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetLight(DWORD idx, D3DLIGHT7 *data) { + Logger::debug(">>> D3D7Device::GetLight"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + HRESULT hr = m_d3d9->GetLight(idx, reinterpret_cast(data)); + if (unlikely(FAILED(hr))) + return DDERR_INVALIDPARAMS; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetRenderState(D3DRENDERSTATETYPE dwRenderStateType, DWORD dwRenderState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(str::format(">>> D3D7Device::SetRenderState: ", dwRenderStateType)); + + // As opposed to D3D8/9, D3D7 actually validates and + // errors out in case of unknown/invalid render states + if (unlikely(!IsValidD3D7RenderStateType(dwRenderStateType))) { + Logger::debug(str::format("D3D7Device::SetRenderState: Invalid render state ", dwRenderStateType)); + return DDERR_INVALIDPARAMS; + } + + d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(dwRenderStateType); + + switch (dwRenderStateType) { + // Most render states translate 1:1 to D3D9 + default: + break; + + case D3DRENDERSTATE_ANTIALIAS: { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (likely(d3dOptions->emulateFSAA == FSAAEmulation::Disabled)) { + if (unlikely(dwRenderState == D3DANTIALIAS_SORTDEPENDENT + || dwRenderState == D3DANTIALIAS_SORTINDEPENDENT)) + Logger::warn("D3D7Device::SetRenderState: Device does not expose FSAA emulation"); + return D3D_OK; + } + + State9 = d3d9::D3DRS_MULTISAMPLEANTIALIAS; + m_antialias = dwRenderState; + dwRenderState = m_antialias == D3DANTIALIAS_SORTDEPENDENT + || m_antialias == D3DANTIALIAS_SORTINDEPENDENT + || d3dOptions->emulateFSAA == FSAAEmulation::Forced ? TRUE : FALSE; + break; + } + + // Always enabled on later APIs, so it can't really be turned off + // Even the D3D7 docs state: "Note that many 3-D adapters apply + // texture perspective correction unconditionally." + case D3DRENDERSTATE_TEXTUREPERSPECTIVE: + return D3D_OK; + + // TODO: Implement D3DRS_LINEPATTERN - vkCmdSetLineRasterizationModeEXT + // and advertise support with D3DPRASTERCAPS_PAT once that is done + case D3DRENDERSTATE_LINEPATTERN: + static bool s_linePatternErrorShown; + + if (!std::exchange(s_linePatternErrorShown, true)) + Logger::warn("D3D7Device::SetRenderState: Unimplemented render state D3DRS_LINEPATTERN"); + + m_linePattern = bit::cast(dwRenderState); + return D3D_OK; + + // Not supported by D3D7 + case D3DRENDERSTATE_ZVISIBLE: + return D3D_OK; + + // TODO: + case D3DRENDERSTATE_STIPPLEDALPHA: + static bool s_stippledAlphaErrorShown; + + if (dwRenderState && !std::exchange(s_stippledAlphaErrorShown, true)) + Logger::warn("D3D7Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_STIPPLEDALPHA"); + + return D3D_OK; + + case D3DRENDERSTATE_EDGEANTIALIAS: + State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE; + break; + + case D3DRENDERSTATE_COLORKEYENABLE: { + m_colorKeyEnabled = dwRenderState; + + const bool validColorKey = m_textures[0] != nullptr ? m_textures[0]->GetCommonSurface()->HasValidColorKey() : false; + m_bridge->SetColorKeyState(m_colorKeyEnabled && validColorKey); + if (m_colorKeyEnabled && validColorKey) { + DDCOLORKEY normalizedColorKey = m_textures[0]->GetCommonSurface()->GetColorKeyNormalized(); + m_bridge->SetColorKey(normalizedColorKey.dwColorSpaceLowValue, + normalizedColorKey.dwColorSpaceHighValue); + } + + return D3D_OK; + } + + case D3DRENDERSTATE_ZBIAS: + State9 = d3d9::D3DRS_DEPTHBIAS; + dwRenderState = bit::cast(static_cast(dwRenderState) * ddrawCaps::ZBIAS_SCALE); + break; + + // TODO: + case D3DRENDERSTATE_EXTENTS: + static bool s_extentsErrorShown; + + if (dwRenderState && !std::exchange(s_extentsErrorShown, true)) + Logger::warn("D3D7Device::SetRenderState: Unimplemented render state D3DRENDERSTATE_EXTENTS"); + + return D3D_OK; + + // Used in conjunction with D3DRENDERSTATE_COLORKEYENABLE + case D3DRENDERSTATE_COLORKEYBLENDENABLE: + m_colorKeyBlendEnabled = dwRenderState; + return D3D_OK; + } + + // This call will never fail + return m_d3d9->SetRenderState(State9, dwRenderState); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetRenderState(D3DRENDERSTATETYPE dwRenderStateType, LPDWORD lpdwRenderState) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(str::format(">>> D3D7Device::GetRenderState: ", dwRenderStateType)); + + if (unlikely(lpdwRenderState == nullptr)) + return DDERR_INVALIDPARAMS; + + // As opposed to D3D8/9, D3D7 actually validates and + // errors out in case of unknown/invalid render states + if (unlikely(!IsValidD3D7RenderStateType(dwRenderStateType))) { + Logger::debug(str::format("D3D7Device::GetRenderState: Invalid render state ", dwRenderStateType)); + return DDERR_INVALIDPARAMS; + } + + d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(dwRenderStateType); + + switch (dwRenderStateType) { + // Most render states translate 1:1 to D3D9 + default: + break; + + case D3DRENDERSTATE_ANTIALIAS: + *lpdwRenderState = m_antialias; + return D3D_OK; + + // Always enabled on later APIs, so it can't really be turned off + // Even the D3D7 docs state: "Note that many 3-D adapters apply + // texture perspective correction unconditionally." + case D3DRENDERSTATE_TEXTUREPERSPECTIVE: + *lpdwRenderState = TRUE; + return D3D_OK; + + case D3DRENDERSTATE_LINEPATTERN: + *lpdwRenderState = bit::cast(m_linePattern); + return D3D_OK; + + // Not supported by D3D7 + case D3DRENDERSTATE_ZVISIBLE: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_STIPPLEDALPHA: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_EDGEANTIALIAS: + State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE; + break; + + case D3DRENDERSTATE_COLORKEYENABLE: + *lpdwRenderState = m_colorKeyEnabled; + return D3D_OK; + + case D3DRENDERSTATE_ZBIAS: { + DWORD bias = 0; + m_d3d9->GetRenderState(d3d9::D3DRS_DEPTHBIAS, &bias); + *lpdwRenderState = static_cast(bit::cast(bias) * ddrawCaps::ZBIAS_SCALE_INV); + return D3D_OK; + } + + case D3DRENDERSTATE_EXTENTS: + *lpdwRenderState = FALSE; + return D3D_OK; + + case D3DRENDERSTATE_COLORKEYBLENDENABLE: + *lpdwRenderState = m_colorKeyBlendEnabled; + return D3D_OK; + } + + // This call will never fail + return m_d3d9->GetRenderState(State9, lpdwRenderState); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::BeginStateBlock() { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::BeginStateBlock"); + + if (unlikely(m_recorder != nullptr)) + return D3DERR_INBEGINSTATEBLOCK; + + HRESULT hr = m_d3d9->BeginStateBlock(); + + if (likely(SUCCEEDED(hr))) { + m_handle++; + auto stateBlockIterPair = m_stateBlocks.emplace(std::piecewise_construct, + std::forward_as_tuple(m_handle), + std::forward_as_tuple(this)); + m_recorder = &stateBlockIterPair.first->second; + m_recorderHandle = m_handle; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::EndStateBlock(LPDWORD lpdwBlockHandle) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::EndStateBlock"); + + if (unlikely(lpdwBlockHandle == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(m_recorder == nullptr)) + return D3DERR_NOTINBEGINSTATEBLOCK; + + Com pStateBlock; + HRESULT hr = m_d3d9->EndStateBlock(&pStateBlock); + + if (likely(SUCCEEDED(hr))) { + m_recorder->SetD3D9(std::move(pStateBlock)); + + *lpdwBlockHandle = m_recorderHandle; + + m_recorder = nullptr; + m_recorderHandle = 0; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::ApplyStateBlock(DWORD dwBlockHandle) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::ApplyStateBlock"); + + // Applications cannot apply a state block while another is being recorded + if (unlikely(ShouldRecord())) + return D3DERR_INBEGINSTATEBLOCK; + + auto stateBlockIter = m_stateBlocks.find(dwBlockHandle); + + if (unlikely(stateBlockIter == m_stateBlocks.end())) { + Logger::err(str::format("D3D7Device::ApplyStateBlock: Invalid dwBlockHandle: ", std::hex, dwBlockHandle)); + return D3DERR_INVALIDSTATEBLOCK; + } + + return stateBlockIter->second.Apply(); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::CaptureStateBlock(DWORD dwBlockHandle) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::CaptureStateBlock"); + + // Applications cannot capture a state block while another is being recorded + if (unlikely(ShouldRecord())) + return D3DERR_INBEGINSTATEBLOCK; + + auto stateBlockIter = m_stateBlocks.find(dwBlockHandle); + + if (unlikely(stateBlockIter == m_stateBlocks.end())) { + Logger::err(str::format("D3D7Device::CaptureStateBlock: Invalid dwBlockHandle: ", std::hex, dwBlockHandle)); + return D3DERR_INVALIDSTATEBLOCK; + } + + return stateBlockIter->second.Capture(); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::DeleteStateBlock(DWORD dwBlockHandle) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::DeleteStateBlock"); + + // Applications cannot delete a state block while another is being recorded + if (unlikely(ShouldRecord())) + return D3DERR_INBEGINSTATEBLOCK; + + auto stateBlockIter = m_stateBlocks.find(dwBlockHandle); + + if (unlikely(stateBlockIter == m_stateBlocks.end())) { + Logger::err(str::format("D3D7Device::DeleteStateBlock: Invalid dwBlockHandle: ", std::hex, dwBlockHandle)); + return D3DERR_INVALIDSTATEBLOCK; + } + + m_stateBlocks.erase(stateBlockIter); + + // Native apparently does drop the handle counter in + // situations where the handle being removed is the + // last allocated handle, which allows some reuse + if (m_handle == dwBlockHandle) + m_handle--; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::CreateStateBlock(D3DSTATEBLOCKTYPE d3dsbType, LPDWORD lpdwBlockHandle) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::CreateStateBlock"); + + if (unlikely(lpdwBlockHandle == nullptr)) + return DDERR_INVALIDPARAMS; + + // Applications cannot create a state block while another is being recorded + if (unlikely(ShouldRecord())) + return D3DERR_INBEGINSTATEBLOCK; + + D3D7StateBlockType stateBlockType = ConvertStateBlockType(d3dsbType); + + if (unlikely(stateBlockType == D3D7StateBlockType::Unknown)) { + Logger::warn(str::format("D3D7Device::CreateStateBlock: Invalid state block type: ", d3dsbType)); + return DDERR_INVALIDPARAMS; + } + + Com pStateBlock9; + HRESULT res = m_d3d9->CreateStateBlock(d3d9::D3DSTATEBLOCKTYPE(d3dsbType), &pStateBlock9); + + if (likely(SUCCEEDED(res))) { + m_handle++; + m_stateBlocks.emplace(std::piecewise_construct, + std::forward_as_tuple(m_handle), + std::forward_as_tuple(this, stateBlockType, pStateBlock9.ptr())); + *lpdwBlockHandle = m_handle; + } + + return res; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::PreLoad(IDirectDrawSurface7 *surface) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::PreLoad"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(surface))) { + Logger::err("D3D7Device::PreLoad: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw7Surface* surface7 = static_cast(surface); + + HRESULT hr = m_proxy->PreLoad(surface7->GetProxied()); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D7Device::PreLoad: Failed to preload proxied surface"); + return hr; + } + + // Make sure the texture or surface is initialized and updated + hr = surface7->InitializeOrUploadD3D9(); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::PreLoad: Failed to initialize/upload D3D9 surface"); + return hr; + } + + // Does not return an HRESULT + surface7->GetD3D9()->PreLoad(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::DrawPrimitive(D3DPRIMITIVETYPE d3dptPrimitiveType, DWORD dwVertexTypeDesc, LPVOID lpvVertices, DWORD dwVertexCount, DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::DrawPrimitive"); + + RefreshLastUsedDevice(); + + if (unlikely(!dwVertexCount)) + return D3D_OK; + + if (unlikely(lpvVertices == nullptr)) + return DDERR_INVALIDPARAMS; + + m_d3d9->SetFVF(dwVertexTypeDesc); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(d3dptPrimitiveType), + GetPrimitiveCount(d3dptPrimitiveType, dwVertexCount), + lpvVertices, + GetFVFSize(dwVertexTypeDesc)); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::DrawPrimitive: Failed D3D9 call to DrawPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::DrawIndexedPrimitive(D3DPRIMITIVETYPE d3dptPrimitiveType, DWORD dwVertexTypeDesc, LPVOID lpvVertices, DWORD dwVertexCount, LPWORD lpwIndices, DWORD dwIndexCount, DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::DrawIndexedPrimitive"); + + RefreshLastUsedDevice(); + + if (unlikely(!dwVertexCount || !dwIndexCount)) + return D3D_OK; + + if (unlikely(lpvVertices == nullptr || lpwIndices == nullptr)) + return DDERR_INVALIDPARAMS; + + m_d3d9->SetFVF(dwVertexTypeDesc); + HRESULT hr = m_d3d9->DrawIndexedPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(d3dptPrimitiveType), + 0, + dwVertexCount, + GetPrimitiveCount(d3dptPrimitiveType, dwIndexCount), + lpwIndices, + d3d9::D3DFMT_INDEX16, + lpvVertices, + GetFVFSize(dwVertexTypeDesc)); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::DrawIndexedPrimitive: Failed D3D9 call to DrawIndexedPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetClipStatus(D3DCLIPSTATUS *clip_status) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::SetClipStatus"); + + if (unlikely(clip_status == nullptr)) + return DDERR_INVALIDPARAMS; + + m_clipStatus = *clip_status; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetClipStatus(D3DCLIPSTATUS *clip_status) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::GetClipStatus"); + + if (unlikely(clip_status == nullptr)) + return DDERR_INVALIDPARAMS; + + *clip_status = m_clipStatus; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::DrawPrimitiveStrided(D3DPRIMITIVETYPE d3dptPrimitiveType, DWORD dwVertexTypeDesc, LPD3DDRAWPRIMITIVESTRIDEDDATA lpVertexArray, DWORD dwVertexCount, DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::DrawPrimitiveStrided"); + + RefreshLastUsedDevice(); + + if (unlikely(!dwVertexCount)) + return D3D_OK; + + if (unlikely(lpVertexArray == nullptr)) + return DDERR_INVALIDPARAMS; + + // Transform strided vertex data to a standard vertex buffer stream + PackedVertexBuffer pvb = TransformStridedtoUP(dwVertexTypeDesc, lpVertexArray, dwVertexCount); + + m_d3d9->SetFVF(dwVertexTypeDesc); + HRESULT hr = m_d3d9->DrawPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(d3dptPrimitiveType), + GetPrimitiveCount(d3dptPrimitiveType, dwVertexCount), + pvb.vertexData.data(), + pvb.stride); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::DrawPrimitiveStrided: Failed D3D9 call to DrawPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::DrawIndexedPrimitiveStrided(D3DPRIMITIVETYPE d3dptPrimitiveType, DWORD dwVertexTypeDesc, LPD3DDRAWPRIMITIVESTRIDEDDATA lpVertexArray, DWORD dwVertexCount, LPWORD lpwIndices, DWORD dwIndexCount, DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::DrawIndexedPrimitiveStrided"); + + RefreshLastUsedDevice(); + + if (unlikely(!dwVertexCount || !dwIndexCount)) + return D3D_OK; + + if (unlikely(lpVertexArray == nullptr || lpwIndices == nullptr)) + return DDERR_INVALIDPARAMS; + + // Transform strided vertex data to a standard vertex buffer stream + PackedVertexBuffer pvb = TransformStridedtoUP(dwVertexTypeDesc, lpVertexArray, dwVertexCount); + + m_d3d9->SetFVF(dwVertexTypeDesc); + HRESULT hr = m_d3d9->DrawIndexedPrimitiveUP( + d3d9::D3DPRIMITIVETYPE(d3dptPrimitiveType), + 0, + dwVertexCount, + GetPrimitiveCount(d3dptPrimitiveType, dwIndexCount), + lpwIndices, + d3d9::D3DFMT_INDEX16, + pvb.vertexData.data(), + pvb.stride); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::DrawIndexedPrimitiveStrided: Failed D3D9 call to DrawIndexedPrimitiveUP"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::DrawPrimitiveVB(D3DPRIMITIVETYPE d3dptPrimitiveType, LPDIRECT3DVERTEXBUFFER7 lpd3dVertexBuffer, DWORD dwStartVertex, DWORD dwNumVertices, DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::DrawPrimitiveVB"); + + RefreshLastUsedDevice(); + + if (unlikely(!dwNumVertices)) + return D3D_OK; + + if (unlikely(lpd3dVertexBuffer == nullptr)) + return DDERR_INVALIDPARAMS; + + Com vb = static_cast(lpd3dVertexBuffer); + + if (unlikely(vb->IsLocked())) { + Logger::err("D3D7Device::DrawPrimitiveVB: Buffer is locked"); + return D3DERR_VERTEXBUFFERLOCKED; + } + + m_d3d9->SetFVF(vb->GetFVF()); + m_d3d9->SetStreamSource(0, vb->GetD3D9(), 0, vb->GetStride()); + HRESULT hr = m_d3d9->DrawPrimitive( + d3d9::D3DPRIMITIVETYPE(d3dptPrimitiveType), + dwStartVertex, + GetPrimitiveCount(d3dptPrimitiveType, dwNumVertices)); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::DrawPrimitiveVB: Failed D3D9 call to DrawPrimitive"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::DrawIndexedPrimitiveVB(D3DPRIMITIVETYPE d3dptPrimitiveType, LPDIRECT3DVERTEXBUFFER7 lpd3dVertexBuffer, DWORD dwStartVertex, DWORD dwNumVertices, LPWORD lpwIndices, DWORD dwIndexCount, DWORD dwFlags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::DrawIndexedPrimitiveVB"); + + RefreshLastUsedDevice(); + + if (unlikely(!dwNumVertices || !dwIndexCount)) + return D3D_OK; + + if (unlikely(lpd3dVertexBuffer == nullptr || lpwIndices == nullptr)) + return DDERR_INVALIDPARAMS; + + Com vb = static_cast(lpd3dVertexBuffer); + + if (unlikely(vb->IsLocked())) { + Logger::err("D3D7Device::DrawIndexedPrimitiveVB: Buffer is locked"); + return D3DERR_VERTEXBUFFERLOCKED; + } + + uint8_t ibIndex = 0; + // Fit index buffer uploads into the smallest buffer size possible + while (dwIndexCount > ddrawCaps::IndexCount[ibIndex]) { + ibIndex++; + if (unlikely(ibIndex > ddrawCaps::IndexBufferCount - 1)) { + Logger::err("D3D7Device::DrawIndexedPrimitiveVB: Exceeded size of largest index buffer"); + return DDERR_GENERIC; + } + } + + d3d9::IDirect3DIndexBuffer9* ib9 = m_ib9[ibIndex].ptr(); + + UploadIndices(ib9, lpwIndices, dwIndexCount); + m_ib9_uploads[ibIndex]++; + m_d3d9->SetIndices(ib9); + m_d3d9->SetFVF(vb->GetFVF()); + m_d3d9->SetStreamSource(0, vb->GetD3D9(), 0, vb->GetStride()); + HRESULT hr = m_d3d9->DrawIndexedPrimitive( + d3d9::D3DPRIMITIVETYPE(d3dptPrimitiveType), + dwStartVertex, + 0, + dwNumVertices, + 0, + GetPrimitiveCount(d3dptPrimitiveType, dwIndexCount)); + + if(unlikely(FAILED(hr))) { + Logger::err("D3D7Device::DrawIndexedPrimitiveVB: Failed D3D9 call to DrawIndexedPrimitive"); + return hr; + } + + m_commonIntf->UpdateDrawTracking(); + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::ComputeSphereVisibility(D3DVECTOR *lpCenters, D3DVALUE *lpRadii, DWORD dwNumSpheres, DWORD dwFlags, DWORD *lpdwReturnValues) { + Logger::debug(">>> D3D7Device::ComputeSphereVisibility"); + + if (unlikely(lpCenters == nullptr || lpRadii == nullptr || lpdwReturnValues == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(dwNumSpheres == 0)) + return D3D_OK; + + // Docs state: "The array need not be initialized, but it must be large enough to contain a DWORD for + // each sphere being tested. When the method returns, each element in the array contains a combination + // of flags that describe the visibility of that sphere within the current viewport for this device. + // If a sphere is completely visible, the corresponding entry in lpdwReturnValues is 0." + // Consider everything to be visible as a minimal implementation, which makes Space Empires V happy. + for (DWORD i = 0; i < dwNumSpheres; i++) + lpdwReturnValues[i] = 0; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetTexture(DWORD stage, IDirectDrawSurface7 **surface) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::GetTexture"); + + if (unlikely(surface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(stage >= ddrawCaps::TextureStageCount)) { + Logger::err(str::format("D3D7Device::GetTexture: Invalid texture stage: ", stage)); + return DDERR_INVALIDPARAMS; + } + + *surface = m_textures[stage].ref(); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetTexture(DWORD stage, IDirectDrawSurface7 *surface) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug(">>> D3D7Device::SetTexture"); + + if (unlikely(stage >= ddrawCaps::TextureStageCount)) { + Logger::err(str::format("D3D7Device::SetTexture: Invalid texture stage: ", stage)); + return DDERR_INVALIDPARAMS; + } + + if (unlikely(ShouldRecord())) + return m_recorder->SetTexture(stage, surface); + + HRESULT hr; + + // Unbinding texture stages + if (surface == nullptr) { + Logger::debug("D3D7Device::SetTexture: Unbiding D3D9 texture"); + + hr = m_d3d9->SetTexture(stage, nullptr); + + if (likely(SUCCEEDED(hr))) { + if (m_textures[stage] != nullptr) { + Logger::debug("D3D7Device::SetTexture: Unbinding local texture"); + m_textures[stage] = nullptr; + + if (likely(stage == 0)) + m_bridge->SetColorKeyState(false); + } + } else { + Logger::err("D3D7Device::SetTexture: Failed to unbind D3D9 texture"); + } + + return hr; + } + + // Binding texture stages + if (unlikely(!m_commonIntf->IsWrappedSurface(surface))) { + Logger::err("D3D7Device::SetTexture: Received an unwrapped texture"); + return DDERR_GENERIC; + } + + Logger::debug("D3D7Device::SetTexture: Binding D3D9 texture"); + + DDraw7Surface* surface7 = static_cast(surface); + + // Only upload textures if any sort of blit/lock operation + // has been performed on them since the last SetTexture call, + // or textures which have been used on a different device, and + // need their D3D9 object to be reinitialized at this point + if (surface7->GetCommonSurface()->HasDirtyMipMaps() || + unlikely(surface7->GetD3D9Device() != m_d3d9.ptr())) { + hr = surface7->InitializeOrUploadD3D9(); + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::SetTexture: Failed to initialize/upload D3D9 texture"); + return hr; + } + + surface7->GetCommonSurface()->UnDirtyMipMaps(); + } else { + Logger::debug("D3D7Device::SetTexture: Skipping upload of texture and mip maps"); + } + + // Only fast skip on D3D9 side, since we want to ensure + // color keying is applied properly even in the case + // of the same texture being set again (color key may change) + //if (unlikely(m_textures[stage] == surface7)) + //return D3D_OK; + + d3d9::IDirect3DTexture9* tex9 = surface7->GetD3D9Texture(); + + if (likely(tex9 != nullptr)) { + hr = m_d3d9->SetTexture(stage, tex9); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D7Device::SetTexture: Failed to bind D3D9 texture"); + return hr; + } + + if (likely(stage == 0)) { + const bool validColorKey = surface7->GetCommonSurface()->HasValidColorKey(); + m_bridge->SetColorKeyState(m_colorKeyEnabled && validColorKey); + if (m_colorKeyEnabled && validColorKey) { + DDCOLORKEY normalizedColorKey = surface7->GetCommonSurface()->GetColorKeyNormalized(); + m_bridge->SetColorKey(normalizedColorKey.dwColorSpaceLowValue, + normalizedColorKey.dwColorSpaceHighValue); + } + } + } else { + d3d9::IDirect3DCubeTexture9* cube9 = surface7->GetD3D9CubeTexture(); + + hr = m_d3d9->SetTexture(stage, cube9); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D7Device::SetTexture: Failed to bind D3D9 cube texture"); + return hr; + } + } + + m_textures[stage] = surface7; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetTextureStageState(DWORD dwStage, D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, LPDWORD lpdwState) { + Logger::debug(">>> D3D7Device::GetTextureStageState"); + + if (unlikely(lpdwState == nullptr)) + return DDERR_INVALIDPARAMS; + + // In the case of D3DTSS_ADDRESS, which is D3D7 specific, + // simply return based on D3DTSS_ADDRESSU + if (d3dTexStageStateType == D3DTSS_ADDRESS) { + return m_d3d9->GetSamplerState(dwStage, d3d9::D3DSAMP_ADDRESSU, lpdwState); + } + + d3d9::D3DSAMPLERSTATETYPE stateType = ConvertSamplerStateType(d3dTexStageStateType); + + // If the type has been remapped to a sampler state type + if (stateType != -1u) { + // MAG/MIN/MIP filter enums are each different than the unified D3D9 D3DTEXTUREFILTERTYPE + if (stateType == d3d9::D3DSAMP_MAGFILTER || + stateType == d3d9::D3DSAMP_MINFILTER || stateType == d3d9::D3DSAMP_MIPFILTER) { + DWORD dwStateProxy9 = 0; + HRESULT hr = m_d3d9->GetSamplerState(dwStage, stateType, &dwStateProxy9); + *lpdwState = DecodeD3D9TexFilterValues(d3dTexStageStateType, dwStateProxy9); + return hr; + } else { + return m_d3d9->GetSamplerState(dwStage, stateType, lpdwState); + } + } else { + return m_d3d9->GetTextureStageState(dwStage, d3d9::D3DTEXTURESTAGESTATETYPE(d3dTexStageStateType), lpdwState); + } + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetTextureStageState(DWORD dwStage, D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, DWORD dwState) { + Logger::debug(">>> D3D7Device::SetTextureStageState"); + + // In the case of D3DTSS_ADDRESS, which is D3D7 specific, + // we need to set up both D3DTSS_ADDRESSU and D3DTSS_ADDRESSV + if (d3dTexStageStateType == D3DTSS_ADDRESS) { + m_d3d9->SetSamplerState(dwStage, d3d9::D3DSAMP_ADDRESSU, dwState); + return m_d3d9->SetSamplerState(dwStage, d3d9::D3DSAMP_ADDRESSV, dwState); + } + + d3d9::D3DSAMPLERSTATETYPE stateType = ConvertSamplerStateType(d3dTexStageStateType); + + // If the type has been remapped to a sampler state type + if (stateType != -1u) { + // MAG/MIN/MIP filter enums are each different than the unified D3D9 D3DTEXTUREFILTERTYPE + if (stateType == d3d9::D3DSAMP_MAGFILTER || + stateType == d3d9::D3DSAMP_MINFILTER || stateType == d3d9::D3DSAMP_MIPFILTER) { + const DWORD dwState9 = DecodeD3D7TexFilterValues(d3dTexStageStateType, dwState); + return m_d3d9->SetSamplerState(dwStage, stateType, dwState9); + } else { + return m_d3d9->SetSamplerState(dwStage, stateType, dwState); + } + } else { + return m_d3d9->SetTextureStageState(dwStage, d3d9::D3DTEXTURESTAGESTATETYPE(d3dTexStageStateType), dwState); + } + } + + HRESULT STDMETHODCALLTYPE D3D7Device::ValidateDevice(LPDWORD lpdwPasses) { + Logger::debug(">>> D3D7Device::ValidateDevice"); + + HRESULT hr = m_d3d9->ValidateDevice(lpdwPasses); + if (unlikely(FAILED(hr))) + return DDERR_INVALIDPARAMS; + + return D3D_OK; + } + + // This is a precursor of our ol' D3D8 pal CopyRects + HRESULT STDMETHODCALLTYPE D3D7Device::Load(IDirectDrawSurface7 *dst_surface, POINT *dst_point, IDirectDrawSurface7 *src_surface, RECT *src_rect, DWORD flags) { + D3DDeviceLock lock = LockDevice(); + + Logger::debug("<<< D3D7Device::Load: Proxy"); + + if (dst_surface == nullptr || src_surface == nullptr) { + Logger::warn("D3D7Device::Load: null source or destination"); + return DDERR_INVALIDPARAMS; + } + + DDraw7Surface* ddraw7SurfaceSrc = nullptr; + DDraw7Surface* ddraw7SurfaceDst = nullptr; + + if (likely(m_commonIntf->IsWrappedSurface(src_surface))) { + ddraw7SurfaceSrc = static_cast(src_surface); + } else { + Logger::warn("D3D7Device::Load: Unwrapped surface source"); + return DDERR_GENERIC; + } + + if (likely(m_commonIntf->IsWrappedSurface(dst_surface))) { + ddraw7SurfaceDst = static_cast(dst_surface); + } else { + Logger::warn("D3D7Device::Load: Unwrapped surface destination"); + return DDERR_GENERIC; + } + + HRESULT hr = m_proxy->Load(ddraw7SurfaceDst->GetProxied(), dst_point, + ddraw7SurfaceSrc->GetProxied(), src_rect, flags); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D7Device::Load: Failed to load surfaces"); + return hr; + } + + // Update the cached destination surface desc + DDSURFACEDESC2 desc2; + desc2.dwSize = sizeof(DDSURFACEDESC2); + HRESULT hrDesc = ddraw7SurfaceDst->GetProxied()->GetSurfaceDesc(&desc2); + + if (unlikely(FAILED(hrDesc))) { + Logger::err("D3D7Device::Load: Failed to retrieve updated destination surface desc"); + } else { + ddraw7SurfaceDst->GetCommonSurface()->SetDesc2(desc2); + } + + // Textures and cubemaps get uploaded during SetTexture calls + if (!ddraw7SurfaceDst->GetCommonSurface()->IsTextureOrCubeMap()) { + HRESULT hrInitDst = ddraw7SurfaceDst->InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrInitDst))) { + Logger::err("D3D7Device::Load: Failed to upload D3D9 destination surface data"); + } + } else { + ddraw7SurfaceDst->GetCommonSurface()->DirtyMipMaps(); + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::LightEnable(DWORD dwLightIndex, BOOL bEnable) { + Logger::debug(">>> D3D7Device::LightEnable"); + return m_d3d9->LightEnable(dwLightIndex, bEnable); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetLightEnable(DWORD dwLightIndex, BOOL *pbEnable) { + Logger::debug(">>> D3D7Device::GetLightEnable"); + + if (unlikely(pbEnable == nullptr)) + return DDERR_INVALIDPARAMS; + + HRESULT hr = m_d3d9->GetLightEnable(dwLightIndex, pbEnable); + if (unlikely(FAILED(hr))) + return DDERR_INVALIDPARAMS; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Device::SetClipPlane(DWORD dwIndex, D3DVALUE *pPlaneEquation) { + Logger::debug(">>> D3D7Device::SetClipPlane"); + return m_d3d9->SetClipPlane(dwIndex, pPlaneEquation); + } + + HRESULT STDMETHODCALLTYPE D3D7Device::GetClipPlane(DWORD dwIndex, D3DVALUE *pPlaneEquation) { + Logger::debug(">>> D3D7Device::GetClipPlane"); + return m_d3d9->GetClipPlane(dwIndex, pPlaneEquation); + } + + // Docs state: "This method returns S_FALSE on retail builds of DirectX." + HRESULT STDMETHODCALLTYPE D3D7Device::GetInfo(DWORD info_id, void *info, DWORD info_size) { + Logger::debug(">>> D3D7Device::GetInfo"); + return S_FALSE; + } + + void D3D7Device::InitializeDS() { + if (!m_rt->IsInitialized()) + m_rt->InitializeD3D9RenderTarget(); + + m_ds = m_rt->GetAttachedDepthStencil(); + + if (m_ds != nullptr) { + Logger::debug("D3D7Device::InitializeDS: Found an attached DS"); + + HRESULT hrDS = m_ds->InitializeD3D9DepthStencil(); + if (unlikely(FAILED(hrDS))) { + Logger::err("D3D7Device::InitializeDS: Failed to initialize D3D9 DS"); + } else { + Logger::info("D3D7Device::InitializeDS: Got depth stencil from RT"); + + DDSURFACEDESC2 descDS; + descDS.dwSize = sizeof(DDSURFACEDESC2); + m_ds->GetProxied()->GetSurfaceDesc(&descDS); + Logger::debug(str::format("D3D7Device::InitializeDS: DepthStencil: ", descDS.dwWidth, "x", descDS.dwHeight)); + + HRESULT hrDS9 = m_d3d9->SetDepthStencilSurface(m_ds->GetD3D9()); + if(unlikely(FAILED(hrDS9))) { + Logger::err("D3D7Device::InitializeDS: Failed to set D3D9 depth stencil"); + } else { + // This needs to act like an auto depth stencil of sorts, so manually enable z-buffering + m_d3d9->SetRenderState(d3d9::D3DRS_ZENABLE, d3d9::D3DZB_TRUE); + } + } + } else { + Logger::info("D3D7Device::InitializeDS: RT has no depth stencil attached"); + m_d3d9->SetDepthStencilSurface(nullptr); + // Should be superfluous, but play it safe + m_d3d9->SetRenderState(d3d9::D3DRS_ZENABLE, d3d9::D3DZB_FALSE); + } + } + + HRESULT D3D7Device::ResetD3D9Swapchain(d3d9::D3DPRESENT_PARAMETERS* params) { + Logger::info("D3D7Device::ResetD3D9Swapchain: Resetting the D3D9 swapchain"); + + HRESULT hr = m_bridge->ResetSwapChain(params); + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Device::ResetD3D9Swapchain: Failed to reset the D3D9 swapchain"); + } else { + // TODO: Cache and reset all surfaces tied to the D3D9 backbuffers + m_rt->SetD3D9(nullptr); + // Note that the D3D9 depth stencil survives a swapchain reset, + // so there's no need to worry about it in this case + } + + return hr; + } + + inline HRESULT D3D7Device::InitializeIndexBuffers() { + static constexpr DWORD Usage = D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY; + + for (uint8_t ibIndex = 0; ibIndex < ddrawCaps::IndexBufferCount ; ibIndex++) { + const UINT ibSize = ddrawCaps::IndexCount[ibIndex] * sizeof(WORD); + + Logger::debug(str::format("D3D7Device::InitializeIndexBuffer: Creating index buffer, size: ", ibSize)); + + HRESULT hr = m_d3d9->CreateIndexBuffer(ibSize, Usage, d3d9::D3DFMT_INDEX16, + d3d9::D3DPOOL_DEFAULT, &m_ib9[ibIndex], nullptr); + if (unlikely(FAILED(hr))) + return hr; + } + + return D3D_OK; + } + + inline void D3D7Device::UploadIndices(d3d9::IDirect3DIndexBuffer9* ib9, WORD* indices, DWORD indexCount) { + Logger::debug(str::format("D3D7Device::UploadIndices: Uploading ", indexCount, " indices")); + + const size_t size = indexCount * sizeof(WORD); + void* pData = nullptr; + + // Locking and unlocking are generally expected to work here + ib9->Lock(0, size, &pData, D3DLOCK_DISCARD); + memcpy(pData, static_cast(indices), size); + ib9->Unlock(); + } + +} diff --git a/src/ddraw/d3d7/d3d7_device.h b/src/ddraw/d3d7/d3d7_device.h new file mode 100644 index 00000000000..101fd627636 --- /dev/null +++ b/src/ddraw/d3d7/d3d7_device.h @@ -0,0 +1,229 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_options.h" +#include "../ddraw_util.h" +#include "../ddraw_caps.h" + +#include "../d3d_multithread.h" +#include "../ddraw_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +#include "d3d7_interface.h" + +#include +#include + +namespace dxvk { + + class DDrawCommonInterface; + class DDraw7Surface; + class D3D7StateBlock; + + /** + * \brief D3D7 device implementation + */ + class D3D7Device final : public DDrawWrappedObject { + + friend class D3D7StateBlock; + + public: + D3D7Device( + Com&& d3d7DeviceProxy, + D3D7Interface* pParent, + D3DDEVICEDESC7 Desc, + d3d9::D3DPRESENT_PARAMETERS Params9, + Com&& pDevice9, + DDraw7Surface* pRT, + DWORD CreationFlags9); + + ~D3D7Device(); + + HRESULT STDMETHODCALLTYPE GetCaps(D3DDEVICEDESC7 *desc); + + HRESULT STDMETHODCALLTYPE EnumTextureFormats(LPD3DENUMPIXELFORMATSCALLBACK cb, void *ctx); + + HRESULT STDMETHODCALLTYPE BeginScene(); + + HRESULT STDMETHODCALLTYPE EndScene(); + + HRESULT STDMETHODCALLTYPE GetDirect3D(IDirect3D7 **d3d); + + HRESULT STDMETHODCALLTYPE SetRenderTarget(IDirectDrawSurface7 *surface, DWORD flags); + + HRESULT STDMETHODCALLTYPE GetRenderTarget(IDirectDrawSurface7 **surface); + + HRESULT STDMETHODCALLTYPE Clear(DWORD count, D3DRECT *rects, DWORD flags, D3DCOLOR color, D3DVALUE z, DWORD stencil); + + HRESULT STDMETHODCALLTYPE SetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE GetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE SetViewport(D3DVIEWPORT7 *data); + + HRESULT STDMETHODCALLTYPE MultiplyTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX *matrix); + + HRESULT STDMETHODCALLTYPE GetViewport(D3DVIEWPORT7 *data); + + HRESULT STDMETHODCALLTYPE SetMaterial(D3DMATERIAL7 *data); + + HRESULT STDMETHODCALLTYPE GetMaterial(D3DMATERIAL7 *data); + + HRESULT STDMETHODCALLTYPE SetLight(DWORD idx, D3DLIGHT7 *data); + + HRESULT STDMETHODCALLTYPE GetLight(DWORD idx, D3DLIGHT7 *data); + + HRESULT STDMETHODCALLTYPE SetRenderState(D3DRENDERSTATETYPE dwRenderStateType, DWORD dwRenderState); + + HRESULT STDMETHODCALLTYPE GetRenderState(D3DRENDERSTATETYPE dwRenderStateType, LPDWORD lpdwRenderState); + + HRESULT STDMETHODCALLTYPE BeginStateBlock(); + + HRESULT STDMETHODCALLTYPE EndStateBlock(LPDWORD lpdwBlockHandle); + + HRESULT STDMETHODCALLTYPE PreLoad(IDirectDrawSurface7 *surface); + + HRESULT STDMETHODCALLTYPE DrawPrimitive(D3DPRIMITIVETYPE primitive_type, DWORD fvf, void *pVertex, DWORD vertexCount, DWORD flags); + + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitive(D3DPRIMITIVETYPE primitive_type, DWORD fvf, void *pVertex, DWORD vertexCount, WORD *pIndex, DWORD indexCount, DWORD flags); + + HRESULT STDMETHODCALLTYPE SetClipStatus(D3DCLIPSTATUS *clip_status); + + HRESULT STDMETHODCALLTYPE GetClipStatus(D3DCLIPSTATUS *clip_status); + + HRESULT STDMETHODCALLTYPE DrawPrimitiveStrided(D3DPRIMITIVETYPE d3dptPrimitiveType, DWORD dwVertexTypeDesc, LPD3DDRAWPRIMITIVESTRIDEDDATA lpVertexArray, DWORD dwVertexCount, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitiveStrided(D3DPRIMITIVETYPE d3dptPrimitiveType, DWORD dwVertexTypeDesc, LPD3DDRAWPRIMITIVESTRIDEDDATA lpVertexArray, DWORD dwVertexCount, LPWORD lpwIndices, DWORD dwIndexCount, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE DrawPrimitiveVB(D3DPRIMITIVETYPE primitive_type, IDirect3DVertexBuffer7 *vb, DWORD startVertex, DWORD primitiveCount, DWORD flags); + + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitiveVB(D3DPRIMITIVETYPE primitive_type, IDirect3DVertexBuffer7 *vb, DWORD startVertex, DWORD primitiveCount, WORD *pIndex, DWORD indexCount, DWORD flags); + + HRESULT STDMETHODCALLTYPE ComputeSphereVisibility(D3DVECTOR *lpCenters, D3DVALUE *lpRadii, DWORD dwNumSpheres, DWORD dwFlags, DWORD *lpdwReturnValues); + + HRESULT STDMETHODCALLTYPE GetTexture(DWORD stage, IDirectDrawSurface7 **surface); + + HRESULT STDMETHODCALLTYPE SetTexture(DWORD stage, IDirectDrawSurface7 *surface); + + HRESULT STDMETHODCALLTYPE GetTextureStageState(DWORD dwStage, D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, LPDWORD lpdwState); + + HRESULT STDMETHODCALLTYPE SetTextureStageState(DWORD dwStage, D3DTEXTURESTAGESTATETYPE d3dTexStageStateType, DWORD dwState); + + HRESULT STDMETHODCALLTYPE ValidateDevice(LPDWORD lpdwPasses); + + HRESULT STDMETHODCALLTYPE ApplyStateBlock(DWORD dwBlockHandle); + + HRESULT STDMETHODCALLTYPE CaptureStateBlock(DWORD dwBlockHandle); + + HRESULT STDMETHODCALLTYPE DeleteStateBlock(DWORD dwBlockHandle); + + HRESULT STDMETHODCALLTYPE CreateStateBlock(D3DSTATEBLOCKTYPE d3dsbType, LPDWORD lpdwBlockHandle); + + HRESULT STDMETHODCALLTYPE Load(IDirectDrawSurface7 *dst_surface, POINT *dst_point, IDirectDrawSurface7 *src_surface, RECT *src_rect, DWORD flags); + + HRESULT STDMETHODCALLTYPE LightEnable(DWORD dwLightIndex, BOOL bEnable); + + HRESULT STDMETHODCALLTYPE GetLightEnable(DWORD dwLightIndex, BOOL *pbEnable); + + HRESULT STDMETHODCALLTYPE SetClipPlane(DWORD dwIndex, D3DVALUE *pPlaneEquation); + + HRESULT STDMETHODCALLTYPE GetClipPlane(DWORD dwIndex, D3DVALUE *pPlaneEquation); + + HRESULT STDMETHODCALLTYPE GetInfo(DWORD info_id, void *info, DWORD info_size); + + void InitializeDS(); + + HRESULT ResetD3D9Swapchain(d3d9::D3DPRESENT_PARAMETERS* params); + + D3DDeviceLock LockDevice() { + return m_multithread.AcquireLock(); + } + + uint32_t GetTotalTextureMemory() const { + return m_totalMemory; + } + + d3d9::D3DPRESENT_PARAMETERS GetPresentParameters() const { + return m_params9; + } + + d3d9::D3DMULTISAMPLE_TYPE GetMultiSampleType() const { + return m_params9.MultiSampleType; + } + + DDraw7Surface* GetRenderTarget() const { + return m_rt.ptr(); + } + + DDraw7Surface* GetDepthStencil() const { + return m_ds.ptr(); + } + + private: + + inline HRESULT InitializeIndexBuffers(); + + inline void UploadIndices(d3d9::IDirect3DIndexBuffer9* ib9, WORD* indices, DWORD indexCount); + + inline bool LogIndexBufferUsageStats() const { + for (uint32_t m_ib9_upload : m_ib9_uploads) { + if (m_ib9_upload > 0) + return true; + } + return false; + } + + inline bool ShouldRecord() const { return m_recorder != nullptr; } + + inline void RefreshLastUsedDevice() { + if (unlikely(m_commonIntf->GetD3D7Device() != this)) + m_commonIntf->SetD3D7Device(this); + } + + bool m_inScene = false; + + static uint32_t s_deviceCount; + uint32_t m_deviceCount = 0; + + uint32_t m_totalMemory = 0; + + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_bridge; + + D3DMultithread m_multithread; + + d3d9::D3DPRESENT_PARAMETERS m_params9; + + D3DDEVICEDESC7 m_desc; + Com m_rt; + Com m_ds; + + std::array, ddrawCaps::TextureStageCount> m_textures; + + D3D7StateBlock* m_recorder = nullptr; + DWORD m_recorderHandle = 0; + DWORD m_handle = 0; + std::unordered_map m_stateBlocks; + + // Value of D3DRENDERSTATE_COLORKEYENABLE + DWORD m_colorKeyEnabled = 0; + // Value of D3DRENDERSTATE_COLORKEYBLENDENABLE + DWORD m_colorKeyBlendEnabled = 0; + // Value of D3DRENDERSTATE_ANTIALIAS + DWORD m_antialias = D3DANTIALIAS_NONE; + // Value of D3DRENDERSTATE_LINEPATTERN + D3DLINEPATTERN m_linePattern = { }; + // Value of D3DCLIPSTATUS + D3DCLIPSTATUS m_clipStatus = { }; + + // Common index buffers used for indexed draws, split up into five sizes: + // XS, S, M, L and XL, corresponding to 0.5 kb, 2 kb, 8 kb, 32 kb and 128 kb + std::array, ddrawCaps::IndexBufferCount> m_ib9; + uint32_t m_ib9_uploads[ddrawCaps::IndexBufferCount] = { }; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d7/d3d7_interface.cpp b/src/ddraw/d3d7/d3d7_interface.cpp new file mode 100644 index 00000000000..db7e65b5f5b --- /dev/null +++ b/src/ddraw/d3d7/d3d7_interface.cpp @@ -0,0 +1,418 @@ +#include "d3d7_interface.h" + +#include "d3d7_device.h" +#include "d3d7_buffer.h" + +#include "../d3d_multithread.h" + +#include "../ddraw7/ddraw7_interface.h" +#include "../ddraw7/ddraw7_surface.h" + +namespace dxvk { + + uint32_t D3D7Interface::s_intfCount = 0; + + D3D7Interface::D3D7Interface( + DDrawCommonInterface* commonIntf, + D3DCommonInterface* commonD3DIntf, + Com&& d3d7IntfProxy, + IUnknown* pParent) + : DDrawWrappedObject(pParent, std::move(d3d7IntfProxy), std::move(d3d9::Direct3DCreate9(D3D_SDK_VERSION))) + , m_commonIntf ( commonIntf ) + , m_commonD3DIntf ( commonD3DIntf ) { + // Get the bridge interface to D3D9. + if (unlikely(FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8InterfaceBridge), reinterpret_cast(&m_bridge))))) { + throw DxvkError("D3D7Interface: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + if (m_commonD3DIntf == nullptr) + m_commonD3DIntf = new D3DCommonInterface(); + + m_commonD3DIntf->SetD3D7Interface(this); + + m_bridge->EnableD3D7CompatibilityMode(); + + m_intfCount = ++s_intfCount; + + Logger::debug(str::format("D3D7Interface: Created a new interface nr. ((7-", m_intfCount, "))")); + } + + D3D7Interface::~D3D7Interface() { + if (m_commonD3DIntf->GetD3D7Interface() == this) + m_commonD3DIntf->SetD3D7Interface(nullptr); + + Logger::debug(str::format("D3D7Interface: Interface nr. ((7-", m_intfCount, ")) bites the dust")); + } + + // Interlocked refcount with the parent IDirectDraw7 + ULONG STDMETHODCALLTYPE D3D7Interface::AddRef() { + if (likely(m_parent != nullptr)) { + IUnknown* origin = m_commonIntf->GetOrigin(); + if (likely(origin != nullptr)) + return origin->AddRef(); + else + return m_parent->AddRef(); + } else { + return ComObjectClamp::AddRef(); + } + } + + // Interlocked refcount with the parent IDirectDraw7 + ULONG STDMETHODCALLTYPE D3D7Interface::Release() { + if (likely(m_parent != nullptr)) { + IUnknown* origin = m_commonIntf->GetOrigin(); + if (likely(origin != nullptr)) + return origin->Release(); + else + return m_parent->Release(); + } else { + return ComObjectClamp::Release(); + } + } + + HRESULT STDMETHODCALLTYPE D3D7Interface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> D3D7Interface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (riid == __uuidof(IDirectDraw7)) { + Logger::debug("D3D7Interface::QueryInterface: Query for IDirectDraw7"); + return m_parent->QueryInterface(riid, ppvObject); + } + // Some games query for legacy ddraw interfaces + if (unlikely(riid == __uuidof(IDirectDraw) + || riid == __uuidof(IDirectDraw2) + || riid == __uuidof(IDirectDraw4))) { + Logger::debug("D3D7Interface::QueryInterface: Query for legacy IDirectDraw"); + return m_parent->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE D3D7Interface::EnumDevices(LPD3DENUMDEVICESCALLBACK7 cb, void *ctx) { + Logger::debug(">>> D3D7Interface::EnumDevices"); + + if (unlikely(cb == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // Ideally we should take all the adapters into account, however + // D3D7 supports one RGB (software emulation) device, one HAL device, + // and one HAL T&L device, all indentified via GUIDs + + // Note: The enumeration order seems to matter for some applications, + // such as (The) Summoner, so always report RGB first, then HAL, then T&L HAL + + // Software emulation, this is expected to be exposed + D3DDEVICEDESC7 desc7RGB = GetD3D7Caps(IID_IDirect3DRGBDevice, d3dOptions); + static char deviceDescRGB[100] = "D7VK RGB"; + static char deviceNameRGB[100] = "D7VK RGB"; + + HRESULT hr = cb(&deviceDescRGB[0], &deviceNameRGB[0], &desc7RGB, ctx); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + // Hardware acceleration (no T&L) + D3DDEVICEDESC7 desc7HAL = GetD3D7Caps(IID_IDirect3DHALDevice, d3dOptions); + static char deviceDescHAL[100] = "D7VK HAL"; + static char deviceNameHAL[100] = "D7VK HAL"; + + hr = cb(&deviceDescHAL[0], &deviceNameHAL[0], &desc7HAL, ctx); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + // Hardware acceleration with T&L + D3DDEVICEDESC7 desc7TNL = GetD3D7Caps(IID_IDirect3DTnLHalDevice, d3dOptions); + static char deviceDescTNL[100] = "D7VK T&L HAL"; + static char deviceNameTNL[100] = "D7VK T&L HAL"; + + hr = cb(&deviceDescTNL[0], &deviceNameTNL[0], &desc7TNL, ctx); + if (hr != D3DENUMRET_OK) + return D3D_OK; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Interface::CreateDevice(REFCLSID rclsid, IDirectDrawSurface7 *surface, IDirect3DDevice7 **ppd3dDevice) { + Logger::debug(">>> D3D7Interface::CreateDevice"); + + if (unlikely(ppd3dDevice == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(ppd3dDevice); + + if (unlikely(surface == nullptr)) { + Logger::err("D3D7Interface::CreateDevice: Null surface provided"); + return DDERR_INVALIDPARAMS; + } + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + DWORD deviceCreationFlags9 = D3DCREATE_SOFTWARE_VERTEXPROCESSING; + bool rgbFallback = false; + + if (likely(!d3dOptions->forceSWVP)) { + if (rclsid == IID_IDirect3DTnLHalDevice) { + Logger::info("D3D7Interface::CreateDevice: Creating an IID_IDirect3DTnLHalDevice device"); + deviceCreationFlags9 = D3DCREATE_HARDWARE_VERTEXPROCESSING; + } else if (rclsid == IID_IDirect3DHALDevice) { + Logger::info("D3D7Interface::CreateDevice: Creating an IID_IDirect3DHALDevice device"); + deviceCreationFlags9 = D3DCREATE_MIXED_VERTEXPROCESSING; + } else if (rclsid == IID_IDirect3DRGBDevice) { + Logger::info("D3D7Interface::CreateDevice: Creating an IID_IDirect3DRGBDevice device"); + } else { + Logger::warn("D3D7Interface::CreateDevice: Unsupported device type, falling back to RGB"); + Logger::warn(str::format(rclsid)); + rgbFallback = true; + } + } + + const IID rclsidOverride = rgbFallback ? IID_IDirect3DRGBDevice : rclsid; + + HWND hWnd = m_commonIntf->GetHWND(); + // Needed to sometimes safely skip intro playback on legacy devices + if (unlikely(hWnd == nullptr)) { + Logger::debug("D3D7Interface::CreateDevice: HWND is NULL"); + } + + Com rt7; + if (unlikely(!m_commonIntf->IsWrappedSurface(surface))) { + Logger::err("D3D7Interface::CreateDevice: Unwrapped surface passed as RT"); + return DDERR_GENERIC; + } else { + rt7 = static_cast(surface); + } + + Com d3d7DeviceProxy; + HRESULT hr = m_proxy->CreateDevice(rclsidOverride, rt7->GetProxied(), &d3d7DeviceProxy); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D7Interface::CreateDevice: Failed to create the proxy device"); + return hr; + } + + DDSURFACEDESC2 desc; + desc.dwSize = sizeof(DDSURFACEDESC2); + surface->GetSurfaceDesc(&desc); + + DWORD backBufferWidth = desc.dwWidth; + DWORD BackBufferHeight = desc.dwHeight; + + if (likely(!d3dOptions->forceProxiedPresent && + d3dOptions->backBufferResize)) { + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Ignore any mode size dimensions when in windowed present mode + if (exclusiveMode) { + DDrawModeSize* modeSize = m_commonIntf->GetModeSize(); + // Wayland apparently needs this for somewhat proper back buffer sizing + if ((modeSize->width && modeSize->width < desc.dwWidth) + || (modeSize->height && modeSize->height < desc.dwHeight)) { + Logger::info("D3D7Interface::CreateDevice: Enforcing mode dimensions"); + + backBufferWidth = modeSize->width; + BackBufferHeight = modeSize->height; + } + } + } + + d3d9::D3DFORMAT backBufferFormat = ConvertFormat(desc.ddpfPixelFormat); + + // Determine the supported AA sample count by querying the D3D9 interface + d3d9::D3DMULTISAMPLE_TYPE multiSampleType = d3d9::D3DMULTISAMPLE_NONE; + if (likely(d3dOptions->emulateFSAA != FSAAEmulation::Disabled)) { + HRESULT hr4S = m_d3d9->CheckDeviceMultiSampleType(0, d3d9::D3DDEVTYPE_HAL, backBufferFormat, + TRUE, d3d9::D3DMULTISAMPLE_4_SAMPLES, NULL); + if (unlikely(FAILED(hr4S))) { + HRESULT hr2S = m_d3d9->CheckDeviceMultiSampleType(0, d3d9::D3DDEVTYPE_HAL, backBufferFormat, + TRUE, d3d9::D3DMULTISAMPLE_2_SAMPLES, NULL); + if (unlikely(FAILED(hr2S))) { + Logger::warn("D3D7Interface::CreateDevice: No MSAA support has been detected"); + } else { + Logger::info("D3D7Interface::CreateDevice: Using 2x MSAA for FSAA emulation"); + multiSampleType = d3d9::D3DMULTISAMPLE_2_SAMPLES; + } + } else { + Logger::info("D3D7Interface::CreateDevice: Using 4x MSAA for FSAA emulation"); + multiSampleType = d3d9::D3DMULTISAMPLE_4_SAMPLES; + } + } else { + Logger::info("D3D7Interface::CreateDevice: FSAA emulation is disabled"); + } + + const DWORD cooperativeLevel = m_commonIntf->GetCooperativeLevel(); + + if ((cooperativeLevel & DDSCL_MULTITHREADED) || d3dOptions->forceMultiThreaded) { + Logger::info("D3D7Interface::CreateDevice: Using thread safe runtime synchronization"); + deviceCreationFlags9 |= D3DCREATE_MULTITHREADED; + } + // DDSCL_FPUSETUP was used exclusively prior to DDraw7 and had the opposite effect + // to DDSCL_FPUPRESERVE. It is still present in DDraw7, now as the default state. + // Some D3D7 applications still specify it explicitly, so account for that regardless. + if (!(cooperativeLevel & DDSCL_FPUSETUP) && (cooperativeLevel & DDSCL_FPUPRESERVE)) + deviceCreationFlags9 |= D3DCREATE_FPU_PRESERVE; + if (cooperativeLevel & DDSCL_NOWINDOWCHANGES) + deviceCreationFlags9 |= D3DCREATE_NOWINDOWCHANGES; + + Logger::info(str::format("D3D7Interface::CreateDevice: Back buffer size: ", desc.dwWidth, "x", desc.dwHeight)); + + DWORD backBufferCount = 0; + if (likely(!d3dOptions->forceSingleBackBuffer)) { + IDirectDrawSurface7* backBuffer = rt7->GetProxied(); + while (backBuffer != nullptr) { + IDirectDrawSurface7* parentSurface = backBuffer; + backBuffer = nullptr; + parentSurface->EnumAttachedSurfaces(&backBuffer, ListBackBufferSurfaces7Callback); + backBufferCount++; + // the swapchain will eventually return to its origin + if (backBuffer == rt7->GetProxied()) + break; + } + } + // Consider the front buffer as well when reporting the overall count + Logger::info(str::format("D3D7Interface::CreateDevice: Back buffer count: ", backBufferCount + 1)); + + // Always appears to be enabled when running in non-exclusive mode + const bool vBlankStatus = m_commonIntf->GetWaitForVBlank(); + + d3d9::D3DPRESENT_PARAMETERS params; + params.BackBufferWidth = backBufferWidth; + params.BackBufferHeight = BackBufferHeight; + params.BackBufferFormat = backBufferFormat; + params.BackBufferCount = backBufferCount; + params.MultiSampleType = multiSampleType; // Controlled through D3DRENDERSTATE_ANTIALIAS + params.MultiSampleQuality = 0; + params.SwapEffect = d3d9::D3DSWAPEFFECT_DISCARD; + params.hDeviceWindow = hWnd; + params.Windowed = TRUE; // Always use windowed, so that we can delegate mode switching to ddraw + params.EnableAutoDepthStencil = FALSE; + params.AutoDepthStencilFormat = d3d9::D3DFMT_UNKNOWN; + params.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; // Needed for back buffer locks + params.FullScreen_RefreshRateInHz = 0; // We'll get the right mode/refresh rate set by ddraw, just play along + params.PresentationInterval = vBlankStatus ? D3DPRESENT_INTERVAL_DEFAULT : D3DPRESENT_INTERVAL_IMMEDIATE; + + Com device9; + hr = m_d3d9->CreateDevice( + D3DADAPTER_DEFAULT, + d3d9::D3DDEVTYPE_HAL, + hWnd, + deviceCreationFlags9, + ¶ms, + &device9 + ); + + if (unlikely(FAILED(hr))) { + Logger::err("D3D7Interface::CreateDevice: Failed to create the D3D9 device"); + return hr; + } + + D3DDEVICEDESC7 desc7 = GetD3D7Caps(rclsidOverride, d3dOptions); + + try{ + Com device7 = new D3D7Device(std::move(d3d7DeviceProxy), this, desc7, + params, std::move(device9), + rt7.ptr(), deviceCreationFlags9); + + // Set the newly created D3D7 device on the common interface + m_commonIntf->SetD3D7Device(device7.ptr()); + // Now that we have a valid D3D9 device pointer, we can initialize the depth stencil (if any) + device7->InitializeDS(); + + *ppd3dDevice = device7.ref(); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Interface::CreateVertexBuffer(D3DVERTEXBUFFERDESC *desc, IDirect3DVertexBuffer7 **ppVertexBuffer, DWORD usage) { + Logger::debug(">>> D3D7Interface::CreateVertexBuffer"); + + if (unlikely(desc == nullptr || ppVertexBuffer == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(ppVertexBuffer); + + Com vertexBuffer7; + // We don't really need a proxy buffer any longer + /*HRESULT hr = m_proxy->CreateVertexBuffer(desc, &vertexBuffer7, usage); + if (unlikely(FAILED(hr))) { + Logger::warn("D3D7Interface::CreateVertexBuffer: Failed to create proxy vertex buffer"); + return hr; + }*/ + + // We need to delay the D3D9 vertex buffer creation as long as possible, to ensure + // that (ideally) we actually have a valid D3D7 device in place when that happens + *ppVertexBuffer = ref(new D3D7VertexBuffer(std::move(vertexBuffer7), nullptr, this, *desc)); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Interface::EnumZBufferFormats(REFCLSID riidDevice, LPD3DENUMPIXELFORMATSCALLBACK cb, LPVOID ctx) { + Logger::debug(">>> D3D7Interface::EnumZBufferFormats"); + + if (unlikely(cb == nullptr)) + return DDERR_INVALIDPARAMS; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + // There are just 3 supported depth stencil formats to worry about + // in D3D9, so let's just enumerate them liniarly, for better clarity + DDPIXELFORMAT depthFormat; + HRESULT hr; + + if (likely(d3dOptions->supportD16)) { + depthFormat = GetZBufferFormat(d3d9::D3DFMT_D16); + hr = cb(&depthFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + } + + depthFormat = GetZBufferFormat(d3d9::D3DFMT_D24X8); + hr = cb(&depthFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + depthFormat = GetZBufferFormat(d3d9::D3DFMT_D24S8); + hr = cb(&depthFormat, ctx); + if (unlikely(hr != D3DENUMRET_OK)) + return D3D_OK; + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3D7Interface::EvictManagedTextures() { + Logger::debug(">>> D3D7Interface::EvictManagedTextures"); + + HRESULT hr = m_proxy->EvictManagedTextures(); + if (unlikely(FAILED(hr))) + return hr; + + D3D7Device* d3d7Device = m_commonIntf->GetD3D7Device(); + if (likely(d3d7Device != nullptr)) { + D3DDeviceLock lock = d3d7Device->LockDevice(); + + HRESULT hr9 = d3d7Device->GetD3D9()->EvictManagedResources(); + if (unlikely(FAILED(hr9))) { + Logger::err("D3D7Interface::EvictManagedTextures: Failed D3D9 managed resource eviction"); + return hr9; + } + } + + return D3D_OK; + } + +} \ No newline at end of file diff --git a/src/ddraw/d3d7/d3d7_interface.h b/src/ddraw/d3d7/d3d7_interface.h new file mode 100644 index 00000000000..90b095bbdaa --- /dev/null +++ b/src/ddraw/d3d7/d3d7_interface.h @@ -0,0 +1,66 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_options.h" +#include "../ddraw_util.h" + +#include "../ddraw_common_interface.h" +#include "../d3d_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +namespace dxvk { + + /** + * \brief D3D7 interface implementation + */ + class D3D7Interface final : public DDrawWrappedObject { + + public: + D3D7Interface( + DDrawCommonInterface* commonIntf, + D3DCommonInterface* commonD3DIntf, + Com&& d3d7Intf, + IUnknown* pParent); + + ~D3D7Interface(); + + ULONG STDMETHODCALLTYPE AddRef(); + + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE EnumDevices(LPD3DENUMDEVICESCALLBACK7 cb, void *ctx); + + HRESULT STDMETHODCALLTYPE CreateDevice(REFCLSID rclsid, IDirectDrawSurface7 *surface, IDirect3DDevice7 **ppd3dDevice); + + HRESULT STDMETHODCALLTYPE CreateVertexBuffer(D3DVERTEXBUFFERDESC *desc, IDirect3DVertexBuffer7 **ppVertexBuffer, DWORD usage); + + HRESULT STDMETHODCALLTYPE EnumZBufferFormats(REFCLSID device_iid, LPD3DENUMPIXELFORMATSCALLBACK cb, LPVOID ctx); + + HRESULT STDMETHODCALLTYPE EvictManagedTextures(); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + D3DCommonInterface* GetCommonD3DInterface() const { + return m_commonD3DIntf.ptr(); + } + + private: + + static uint32_t s_intfCount; + uint32_t m_intfCount = 0; + + Com m_bridge; + + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_commonD3DIntf; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d7/d3d7_state_block.cpp b/src/ddraw/d3d7/d3d7_state_block.cpp new file mode 100644 index 00000000000..902dddae194 --- /dev/null +++ b/src/ddraw/d3d7/d3d7_state_block.cpp @@ -0,0 +1,69 @@ +#include "d3d7_state_block.h" + +#include "d3d7_device.h" + +namespace dxvk { + + D3D7StateBlock::D3D7StateBlock( + D3D7Device* pDevice, + D3D7StateBlockType Type, + Com&& pStateBlock) + : m_device ( pDevice ) + , m_stateBlock ( std::move(pStateBlock) ) { + if (Type == D3D7StateBlockType::All) { + m_captures.flags.set(D3D7CapturedStateFlag::Textures); + m_captures.textures.setAll(); + } + + m_state.textures.fill(nullptr); + + // Automatically capture state on creation via D3D8Device::CreateStateBlock. + if (Type != D3D7StateBlockType::None) + Capture(); + } + + // Construct a state block without a D3D9 object + D3D7StateBlock::D3D7StateBlock(D3D7Device* pDevice) + : D3D7StateBlock(pDevice, D3D7StateBlockType::None, nullptr) { + } + + // Attach a D3D9 object to a state block that doesn't have one yet + void D3D7StateBlock::SetD3D9(Com&& pStateBlock) { + if (likely(m_stateBlock == nullptr)) { + m_stateBlock = std::move(pStateBlock); + } else { + Logger::err("D3D7StateBlock::SetD3D9: m_stateBlock has already been initialized"); + } + } + + HRESULT D3D7StateBlock::Capture() { + if (unlikely(m_stateBlock == nullptr)) + return D3DERR_INVALIDCALL; + + if (m_captures.flags.test(D3D7CapturedStateFlag::Textures)) { + for (DWORD stage = 0; stage < m_state.textures.size(); stage++) { + if (m_captures.textures.get(stage)) + m_state.textures[stage] = m_device->m_textures[stage].ptr(); + } + } + + return m_stateBlock->Capture(); + } + + HRESULT D3D7StateBlock::Apply() { + if (unlikely(m_stateBlock == nullptr)) + return D3DERR_INVALIDCALL; + + HRESULT res = m_stateBlock->Apply(); + + if (m_captures.flags.test(D3D7CapturedStateFlag::Textures)) { + for (DWORD stage = 0; stage < m_state.textures.size(); stage++) { + if (m_captures.textures.get(stage)) + m_device->SetTexture(stage, m_state.textures[stage]); + } + } + + return res; + } + +} diff --git a/src/ddraw/d3d7/d3d7_state_block.h b/src/ddraw/d3d7/d3d7_state_block.h new file mode 100644 index 00000000000..294d74e7746 --- /dev/null +++ b/src/ddraw/d3d7/d3d7_state_block.h @@ -0,0 +1,91 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_caps.h" + +#include "../../util/util_bit.h" +#include "../../util/util_flags.h" + +#include "../ddraw7/ddraw7_surface.h" + +#include + +namespace dxvk { + + class D3D7Device; + + enum class D3D7CapturedStateFlag : uint8_t { + Textures + }; + + using D3D7CapturedStateFlags = Flags; + + struct D3D7StateCaptures { + D3D7CapturedStateFlags flags; + + bit::bitset textures; + + D3D7StateCaptures() { + // Ensure all bits are initialized to false + textures.clearAll(); + } + }; + + struct D3D7CapturableState { + std::array textures; + }; + + enum class D3D7StateBlockType : uint8_t { + None, + All, + PixelState, + VertexState, + Unknown + }; + + inline D3D7StateBlockType ConvertStateBlockType(D3DSTATEBLOCKTYPE type) { + switch (type) { + case D3DSBT_ALL: return D3D7StateBlockType::All; + case D3DSBT_PIXELSTATE: return D3D7StateBlockType::PixelState; + case D3DSBT_VERTEXSTATE: return D3D7StateBlockType::VertexState; + default: return D3D7StateBlockType::Unknown; + } + } + + // Wrapper class for D3D9 state blocks. Captures D3D7-specific state. + class D3D7StateBlock { + + public: + + D3D7StateBlock( + D3D7Device* pDevice, + D3D7StateBlockType Type, + Com&& pStateBlock); + + D3D7StateBlock(D3D7Device* pDevice); + + void SetD3D9(Com&& pStateBlock); + + HRESULT Capture(); + + HRESULT Apply(); + + inline HRESULT SetTexture(DWORD Stage, IDirectDrawSurface7* pTexture) { + m_state.textures[Stage] = pTexture; + m_captures.flags.set(D3D7CapturedStateFlag::Textures); + m_captures.textures.set(Stage, true); + return D3D_OK; + } + + private: + + D3D7Device* m_device = nullptr; + + Com m_stateBlock; + + D3D7CapturableState m_state; + D3D7StateCaptures m_captures; + + }; + +} diff --git a/src/ddraw/d3d_common_interface.cpp b/src/ddraw/d3d_common_interface.cpp new file mode 100644 index 00000000000..2643d2b6217 --- /dev/null +++ b/src/ddraw/d3d_common_interface.cpp @@ -0,0 +1,54 @@ +#include "d3d_common_interface.h" + +#include "d3d_common_material.h" + +namespace dxvk { + + D3DCommonInterface::D3DCommonInterface() { + } + + D3DCommonInterface::~D3DCommonInterface() { + } + + d3d9::D3DMATERIAL9* D3DCommonInterface::GetD3D9MaterialFromHandle(D3DMATERIALHANDLE handle) const { + if (unlikely(handle == 0)) + return nullptr; + + auto materialsIter = m_materials.find(handle); + + if (unlikely(materialsIter == m_materials.end())) { + Logger::warn(str::format("D3DCommonInterface::GetD3D9MaterialFromHandle: Unknown handle: ", handle)); + return nullptr; + } + + return materialsIter->second->GetD3D9Material(); + } + + D3DCommonMaterial* D3DCommonInterface::GetCommonMaterialFromHandle(D3DMATERIALHANDLE handle) const { + if (unlikely(handle == 0)) + return nullptr; + + auto materialsIter = m_materials.find(handle); + + if (unlikely(materialsIter == m_materials.end())) { + Logger::warn(str::format("D3DCommonInterface::GetCommonMaterialFromHandle: Unknown handle: ", handle)); + return nullptr; + } + + return materialsIter->second; + } + + void D3DCommonInterface::EmplaceMaterial(D3DCommonMaterial* commonMaterial, D3DMATERIALHANDLE handle) { + m_materials.emplace(std::piecewise_construct, + std::forward_as_tuple(handle), + std::forward_as_tuple(commonMaterial)); + } + + void D3DCommonInterface::ReleaseMaterialHandle(D3DMATERIALHANDLE handle) { + auto materialsIter = m_materials.find(handle); + + if (likely(materialsIter != m_materials.end())) + m_materials.erase(materialsIter); + } + +} \ No newline at end of file diff --git a/src/ddraw/d3d_common_interface.h b/src/ddraw/d3d_common_interface.h new file mode 100644 index 00000000000..653244ebe2e --- /dev/null +++ b/src/ddraw/d3d_common_interface.h @@ -0,0 +1,86 @@ +#pragma once + +#include "ddraw_include.h" + +#include + +namespace dxvk { + + class D3DCommonMaterial; + + class D3D7Interface; + class D3D6Interface; + class D3D5Interface; + class D3D3Interface; + + class D3DCommonInterface : public ComObjectClamp { + + public: + + D3DCommonInterface(); + + ~D3DCommonInterface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { + *ppvObject = this; + return S_OK; + } + + d3d9::D3DMATERIAL9* GetD3D9MaterialFromHandle(D3DMATERIALHANDLE handle) const; + + D3DCommonMaterial* GetCommonMaterialFromHandle(D3DMATERIALHANDLE handle) const; + + void EmplaceMaterial(D3DCommonMaterial* commonMaterial, D3DMATERIALHANDLE handle); + + void ReleaseMaterialHandle(D3DMATERIALHANDLE handle); + + D3DMATERIALHANDLE GetNextMaterialHandle() { + return ++m_materialHandle; + } + + void SetD3D7Interface(D3D7Interface* d3d7Intf) { + m_d3d7Intf = d3d7Intf; + } + + D3D7Interface* GetD3D7Interface() const { + return m_d3d7Intf; + } + + void SetD3D6Interface(D3D6Interface* d3d6Intf) { + m_d3d6Intf = d3d6Intf; + } + + D3D6Interface* GetD3D6Interface() const { + return m_d3d6Intf; + } + + void SetD3D5Interface(D3D5Interface* d3d5Intf) { + m_d3d5Intf = d3d5Intf; + } + + D3D5Interface* GetD3D5Interface() const { + return m_d3d5Intf; + } + + void SetD3D3Interface(D3D3Interface* d3d3Intf) { + m_d3d3Intf = d3d3Intf; + } + + D3D3Interface* GetD3D3Interface() const { + return m_d3d3Intf; + } + + private: + + // Track all possible last used D3D interfaces + D3D7Interface* m_d3d7Intf = nullptr; + D3D6Interface* m_d3d6Intf = nullptr; + D3D5Interface* m_d3d5Intf = nullptr; + D3D3Interface* m_d3d3Intf = nullptr; + + std::atomic m_materialHandle = 0; + std::unordered_map m_materials; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d_common_material.cpp b/src/ddraw/d3d_common_material.cpp new file mode 100644 index 00000000000..d7524520710 --- /dev/null +++ b/src/ddraw/d3d_common_material.cpp @@ -0,0 +1,12 @@ +#include "d3d_common_material.h" + +namespace dxvk { + + D3DCommonMaterial::D3DCommonMaterial(D3DMATERIALHANDLE materialHandle) + : m_materialHandle ( materialHandle ) { + } + + D3DCommonMaterial::~D3DCommonMaterial() { + } + +} \ No newline at end of file diff --git a/src/ddraw/d3d_common_material.h b/src/ddraw/d3d_common_material.h new file mode 100644 index 00000000000..187208861f0 --- /dev/null +++ b/src/ddraw/d3d_common_material.h @@ -0,0 +1,41 @@ +#pragma once + +#include "ddraw_include.h" + +namespace dxvk { + + class D3DCommonMaterial : public ComObjectClamp { + + public: + + D3DCommonMaterial(D3DMATERIALHANDLE materialHandle); + + ~D3DCommonMaterial(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { + *ppvObject = this; + return S_OK; + } + + d3d9::D3DMATERIAL9* GetD3D9Material() { + return &m_material9; + } + + D3DMATERIALHANDLE GetMaterialHandle() const { + return m_materialHandle; + } + + D3DCOLOR GetMaterialColor() const { + return D3DCOLOR_COLORVALUE(m_material9.Diffuse.r, m_material9.Diffuse.g, + m_material9.Diffuse.b, m_material9.Diffuse.a); + } + + private: + + D3DMATERIALHANDLE m_materialHandle = 0; + + d3d9::D3DMATERIAL9 m_material9 = { }; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d_common_texture.cpp b/src/ddraw/d3d_common_texture.cpp new file mode 100644 index 00000000000..195b2bfb279 --- /dev/null +++ b/src/ddraw/d3d_common_texture.cpp @@ -0,0 +1,13 @@ +#include "d3d_common_texture.h" + +namespace dxvk { + + D3DCommonTexture::D3DCommonTexture(DDrawCommonSurface* commonSurf, D3DTEXTUREHANDLE textureHandle) + : m_commonSurf ( commonSurf ) + , m_textureHandle ( textureHandle ) { + } + + D3DCommonTexture::~D3DCommonTexture() { + } + +} \ No newline at end of file diff --git a/src/ddraw/d3d_common_texture.h b/src/ddraw/d3d_common_texture.h new file mode 100644 index 00000000000..d2a10f0546f --- /dev/null +++ b/src/ddraw/d3d_common_texture.h @@ -0,0 +1,46 @@ +#pragma once + +#include "ddraw_include.h" +#include "ddraw_format.h" + +#include "ddraw_common_surface.h" + +namespace dxvk { + + class DDrawSurface; + + class D3DCommonTexture : public ComObjectClamp { + + public: + + D3DCommonTexture(DDrawCommonSurface* commonSurf, D3DTEXTUREHANDLE textureHandle); + + ~D3DCommonTexture(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { + *ppvObject = this; + return S_OK; + } + + // Needed for SwapTextureHandles device calls + void SetTextureHandle(D3DTEXTUREHANDLE handle) { + m_textureHandle = handle; + } + + D3DTEXTUREHANDLE GetTextureHandle() const { + return m_textureHandle; + } + + DDrawSurface* GetDDSurface() const { + return m_commonSurf->GetDDSurface(); + } + + private: + + DDrawCommonSurface* m_commonSurf = nullptr; + + D3DTEXTUREHANDLE m_textureHandle = 0; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d_common_viewport.cpp b/src/ddraw/d3d_common_viewport.cpp new file mode 100644 index 00000000000..ca401f64965 --- /dev/null +++ b/src/ddraw/d3d_common_viewport.cpp @@ -0,0 +1,61 @@ +#include "d3d_common_viewport.h" + +#include "d3d_common_interface.h" + +#include "d3d3/d3d3_device.h" +#include "d3d5/d3d5_device.h" +#include "d3d6/d3d6_device.h" + +namespace dxvk { + + D3DCommonViewport::D3DCommonViewport(D3DCommonInterface* commonD3DIntf) + : m_commonD3DIntf ( commonD3DIntf ) { + } + + D3DCommonViewport::~D3DCommonViewport() { + } + + D3D6Viewport* D3DCommonViewport::GetCurrentD3D6Viewport() { + if (m_device6 != nullptr) + return m_device6->GetCurrentViewportInternal(); + + return nullptr; + } + + D3D5Viewport* D3DCommonViewport::GetCurrentD3D5Viewport() { + if (m_device5 != nullptr) + return m_device5->GetCurrentViewportInternal(); + + return nullptr; + } + + D3D3Viewport* D3DCommonViewport::GetCurrentD3D3Viewport() { + if (m_device3 != nullptr) + return m_device3->GetCurrentViewportInternal(); + + return nullptr; + } + + void D3DCommonViewport::EnableLegacyLights(bool isD3DLight2) { + if (m_device6 != nullptr) { + return m_device6->EnableLegacyLights(isD3DLight2); + } else if (m_device5 != nullptr) { + return m_device5->EnableLegacyLights(isD3DLight2); + } else if (m_device3 != nullptr) { + return m_device3->EnableLegacyLights(isD3DLight2); + } + } + + d3d9::IDirect3DDevice9* D3DCommonViewport::GetD3D9Device() { + if (m_device6 != nullptr) { + return m_device6->GetD3D9(); + } else if (m_device5 != nullptr) { + return m_device5->GetD3D9(); + } else if (m_device3 != nullptr) { + return m_device3->GetD3D9(); + } + + return nullptr; + } + +} \ No newline at end of file diff --git a/src/ddraw/d3d_common_viewport.h b/src/ddraw/d3d_common_viewport.h new file mode 100644 index 00000000000..ddc51538af5 --- /dev/null +++ b/src/ddraw/d3d_common_viewport.h @@ -0,0 +1,218 @@ +#pragma once + +#include "ddraw_include.h" + +#include "d3d_light.h" + +#include + +namespace dxvk { + + class D3DCommonInterface; + + class D3D6Viewport; + class D3D5Viewport; + class D3D3Viewport; + + class D3D6Device; + class D3D5Device; + class D3D3Device; + + class D3DCommonViewport : public ComObjectClamp { + + public: + + D3DCommonViewport(D3DCommonInterface* commonD3DIntf); + + ~D3DCommonViewport(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { + *ppvObject = this; + return S_OK; + } + + D3D6Viewport* GetCurrentD3D6Viewport(); + + D3D5Viewport* GetCurrentD3D5Viewport(); + + D3D3Viewport* GetCurrentD3D3Viewport(); + + void EnableLegacyLights(bool isD3DLight2); + + d3d9::IDirect3DDevice9* GetD3D9Device(); + + D3DCommonInterface* GetCommonD3DInterface() const { + return m_commonD3DIntf; + } + + d3d9::D3DVIEWPORT9* GetD3D9Viewport() { + return &m_viewport9; + } + + std::vector>& GetLights() { + return m_lights; + } + + void MarkViewportAsSet() { + m_isViewportSet = true; + // Also dirty legacy projection on any viewport updates + m_dirtyProjection = true; + } + + bool IsViewportSet() const { + return m_isViewportSet; + } + + void MarkMaterialAsSet() { + m_isMaterialSet = true; + } + + bool IsMaterialSet() const { + return m_isMaterialSet; + } + + void SetMaterialHandle(D3DMATERIALHANDLE materialHandle) { + m_materialHandle = materialHandle; + } + + D3DMATERIALHANDLE GetMaterialHandle() const { + return m_materialHandle; + } + + void SetIsCurrentViewport(bool isCurrentViewport) { + m_isCurrentViewport = isCurrentViewport; + } + + bool IsCurrentViewport() const { + return m_isCurrentViewport; + } + + void SetD3D6Viewport(D3D6Viewport* d3d6Viewport) { + m_d3d6Viewport = d3d6Viewport; + } + + D3D6Viewport* GetD3D6Viewport() const { + return m_d3d6Viewport; + } + + void SetD3D5Viewport(D3D5Viewport* d3d5Viewport) { + m_d3d5Viewport = d3d5Viewport; + } + + D3D5Viewport* GetD3D5Viewport() const { + return m_d3d5Viewport; + } + + void SetD3D3Viewport(D3D3Viewport* d3d3Viewport) { + m_d3d3Viewport = d3d3Viewport; + } + + D3D3Viewport* GetD3D3Viewport() const { + return m_d3d3Viewport; + } + + void SetD3D6Device(D3D6Device* device6) { + m_device6 = device6; + } + + D3D6Device* GetD3D6Device() const { + return m_device6; + } + + void SetD3D5Device(D3D5Device* device5) { + m_device5 = device5; + } + + D3D5Device* GetD3D5Device() const { + return m_device5; + } + + void SetD3D3Device(D3D3Device* device3) { + m_device3 = device3; + } + + D3D3Device* GetD3D3Device() const { + return m_device3; + } + + bool HasDevice() const { + return m_device6 != nullptr || m_device5 != nullptr || m_device3 != nullptr; + } + + D3DVECTOR* GetLegacyScale() { + return &m_legacyScale; + } + + D3DVECTOR* GetLegacyClip() { + return &m_legacyClip; + } + + const D3DMATRIX* GetLegacyProjectionMatrix(DWORD drawFlags) { + // Fast skip if viewport values haven't been set + if (unlikely(!m_isViewportSet)) + return nullptr; + + const bool needsClipping = !(drawFlags & D3DDP_DONOTCLIP); + if (unlikely(m_needsClipping != needsClipping)) { + m_dirtyProjection = true; + m_needsClipping = needsClipping; + } + + // Recalculate legacy projection matrix only when needed + if (unlikely(m_dirtyProjection)) { + m_legacyProjection._11 = m_legacyScale.x; + m_legacyProjection._22 = m_legacyScale.y; + m_legacyProjection._33 = m_legacyScale.z; + m_legacyProjection._41 = m_needsClipping ? m_legacyClip.x : 0.0f; + m_legacyProjection._42 = m_needsClipping ? m_legacyClip.y : 0.0f; + m_legacyProjection._43 = m_needsClipping ? m_legacyClip.z : 0.0f; + m_legacyProjection._44 = 1.0f; + // Determine if the projection matrix is an identity matrix + m_isIdentityMatrix = m_legacyProjection._11 == 1.0f && + m_legacyProjection._22 == 1.0f && + m_legacyProjection._33 == 1.0f && + m_legacyProjection._41 == 0.0f && + m_legacyProjection._42 == 0.0f && + m_legacyProjection._43 == 0.0f; + m_dirtyProjection = false; + } + + return m_isIdentityMatrix ? nullptr : &m_legacyProjection; + } + + private: + + bool m_isViewportSet = false; + bool m_isCurrentViewport = false; + bool m_isMaterialSet = false; + + // Legacy projection state + bool m_isIdentityMatrix = false; + bool m_needsClipping = false; + bool m_dirtyProjection = false; + + D3DCommonInterface* m_commonD3DIntf = nullptr; + + D3DMATERIALHANDLE m_materialHandle = 0; + + d3d9::D3DVIEWPORT9 m_viewport9 = { }; + + D3DVECTOR m_legacyScale = { }; + D3DVECTOR m_legacyClip = { }; + D3DMATRIX m_legacyProjection = { }; + + // Track all possible viewport versions of the same object + D3D6Viewport* m_d3d6Viewport = nullptr; + D3D5Viewport* m_d3d5Viewport = nullptr; + D3D3Viewport* m_d3d3Viewport = nullptr; + + // Track all devices this viewport is attached to + D3D6Device* m_device6 = nullptr; + D3D5Device* m_device5 = nullptr; + D3D3Device* m_device3 = nullptr; + + std::vector> m_lights; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/d3d_light.cpp b/src/ddraw/d3d_light.cpp new file mode 100644 index 00000000000..0d6f972fd4e --- /dev/null +++ b/src/ddraw/d3d_light.cpp @@ -0,0 +1,135 @@ +#include "d3d_light.h" + +#include "d3d3/d3d3_viewport.h" +#include "d3d5/d3d5_viewport.h" +#include "d3d6/d3d6_viewport.h" +#include + +namespace dxvk { + + uint32_t D3DLight::s_lightCount = 0; + + D3DLight::D3DLight(Com&& proxyLight, IUnknown* pParent) + : DDrawWrappedObject(pParent, std::move(proxyLight), nullptr) { + m_lightCount = ++s_lightCount; + + Logger::debug(str::format("D3DLight: Created a new light nr. [[1-", m_lightCount, "]]")); + } + + D3DLight::~D3DLight() { + Logger::debug(str::format("D3DLight: Light nr. [[1-", m_lightCount, "]] bites the dust")); + } + + // Docs state: "The method returns DDERR_ALREADYINITIALIZED because + // the Direct3DLight object is initialized when it is created." + HRESULT STDMETHODCALLTYPE D3DLight::Initialize(IDirect3D *d3d) { + Logger::debug(">>> D3DLight::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE D3DLight::SetLight(D3DLIGHT *data) { + Logger::debug(">>> D3DLight::SetLight"); + + static constexpr D3DCOLORVALUE noLight = {{0.0f}, {0.0f}, {0.0f}, {0.0f}}; + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!data->dwSize)) + return DDERR_INVALIDPARAMS; + + // Hidden & Dangeous spams a lot of parallel point lights + if (unlikely(data->dltType == D3DLIGHT_PARALLELPOINT)) { + static bool s_parallelPointErrorShown; + + if (!std::exchange(s_parallelPointErrorShown, true)) + Logger::warn("D3DLight::SetLight: Unsupported light type D3DLIGHT_PARALLELPOINT"); + + return DDERR_INVALIDPARAMS; + } + // Specific to D3D3 + if (unlikely(data->dltType == D3DLIGHT_GLSPOT)) { + static bool s_glSpotErrorShown; + + if (!std::exchange(s_glSpotErrorShown, true)) + Logger::warn("D3DLight::SetLight: Unsupported light type D3DLIGHT_GLSPOT"); + + return DDERR_INVALIDPARAMS; + } + + m_isD3DLight2 = data->dwSize == sizeof(D3DLIGHT2); + + const bool hasSpecular = m_isD3DLight2 ? !(reinterpret_cast(data)->dwFlags & D3DLIGHT_NO_SPECULAR) : true; + + // Docs: "Although this method's declaration specifies the lpLight parameter as being + // the address of a D3DLIGHT structure, that structure is not normally used. Rather, + // the D3DLIGHT2 structure is recommended to achieve the best lighting effects." + m_light9.Type = d3d9::D3DLIGHTTYPE(data->dltType); + m_light9.Diffuse = data->dcvColor; + m_light9.Specular = hasSpecular ? data->dcvColor : noLight; + // Ambient light comes from the material + m_light9.Ambient = noLight; + m_light9.Position = data->dvPosition; + m_light9.Direction = data->dvDirection; + m_light9.Range = data->dvRange; + m_light9.Falloff = data->dvFalloff; + m_light9.Attenuation0 = data->dvAttenuation0; + m_light9.Attenuation1 = data->dvAttenuation1; + m_light9.Attenuation2 = data->dvAttenuation2; + m_light9.Theta = data->dvTheta; + m_light9.Phi = data->dvPhi; + // D3DLIGHT structure lights are, apparently, considered to be active by default + m_isActive = m_isD3DLight2 ? (reinterpret_cast(data)->dwFlags & D3DLIGHT_ACTIVE) : true; + + Logger::debug(str::format(">>> D3DLight::SetLight: Updated light nr. ", m_lightCount)); + Logger::debug(str::format(" Type: ", m_light9.Type)); + Logger::debug(str::format(" Diffuse: ", m_light9.Diffuse.r, " ", m_light9.Diffuse.g, " ", m_light9.Diffuse.b)); + Logger::debug(str::format(" Specular: ", m_light9.Specular.r, " ", m_light9.Specular.g, " ", m_light9.Specular.b)); + Logger::debug(str::format(" Ambient: ", m_light9.Ambient.r, " ", m_light9.Ambient.g, " ", m_light9.Ambient.b)); + Logger::debug(str::format(" Position: ", m_light9.Position.x, " ", m_light9.Position.y, " ", m_light9.Position.z)); + Logger::debug(str::format(" Direction: ", m_light9.Direction.x, " ", m_light9.Direction.y, " ", m_light9.Direction.z)); + Logger::debug(str::format(" Range: ", m_light9.Range)); + Logger::debug(str::format(" Falloff: ", m_light9.Falloff)); + Logger::debug(str::format(" Attenuation0: ", m_light9.Attenuation0)); + Logger::debug(str::format(" Attenuation1: ", m_light9.Attenuation1)); + Logger::debug(str::format(" Attenuation2: ", m_light9.Attenuation2)); + Logger::debug(str::format(" Theta: ", m_light9.Theta)); + Logger::debug(str::format(" Phi: ", m_light9.Phi)); + + // Update the D3D9 light directly if it's actively being used + if (m_viewport6 != nullptr && m_viewport6->GetCommonViewport()->IsCurrentViewport()) + m_viewport6->ApplyAndActivateLight(m_lightCount, this); + else if (m_viewport5 != nullptr && m_viewport5->GetCommonViewport()->IsCurrentViewport()) + m_viewport5->ApplyAndActivateLight(m_lightCount, this); + else if (m_viewport3 != nullptr && m_viewport3->GetCommonViewport()->IsCurrentViewport()) + m_viewport3->ApplyAndActivateLight(m_lightCount, this); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE D3DLight::GetLight(D3DLIGHT *data) { + Logger::debug(">>> D3DLight::GetLight"); + + if (unlikely(data == nullptr)) + return DDERR_INVALIDPARAMS; + + data->dltType = D3DLIGHTTYPE(m_light9.Type); + data->dcvColor = m_light9.Diffuse; + //data->dcvColor = m_light9.Specular; + data->dvPosition = m_light9.Position; + data->dvDirection = m_light9.Direction; + data->dvRange = m_light9.Range; + data->dvFalloff = m_light9.Falloff; + data->dvAttenuation0 = m_light9.Attenuation0; + data->dvAttenuation1 = m_light9.Attenuation1; + data->dvAttenuation2 = m_light9.Attenuation2; + data->dvTheta = m_light9.Theta; + data->dvPhi = m_light9.Phi; + + if (m_isD3DLight2) + reinterpret_cast(data)->dwFlags = m_isActive; + + return D3D_OK; + } + +} diff --git a/src/ddraw/d3d_light.h b/src/ddraw/d3d_light.h new file mode 100644 index 00000000000..ec0754d71e1 --- /dev/null +++ b/src/ddraw/d3d_light.h @@ -0,0 +1,74 @@ +#pragma once + +#include "ddraw_include.h" +#include "ddraw_wrapped_object.h" + +namespace dxvk { + + class D3D3Viewport; + class D3D5Viewport; + class D3D6Viewport; + + class D3DLight final : public DDrawWrappedObject { + + public: + + D3DLight(Com&& proxyLight, IUnknown* pParent); + + ~D3DLight(); + + HRESULT STDMETHODCALLTYPE Initialize(IDirect3D *d3d); + + HRESULT STDMETHODCALLTYPE SetLight(D3DLIGHT *data); + + HRESULT STDMETHODCALLTYPE GetLight(D3DLIGHT *data); + + const d3d9::D3DLIGHT9* GetD3D9Light() const { + return &m_light9; + } + + void SetViewport6(D3D6Viewport* viewport6) { + m_viewport6 = viewport6; + } + + void SetViewport5(D3D5Viewport* viewport5) { + m_viewport5 = viewport5; + } + + void SetViewport3(D3D3Viewport* viewport3) { + m_viewport3 = viewport3; + } + + bool HasViewport() const { + return m_viewport6 != nullptr || m_viewport5 != nullptr || m_viewport3 != nullptr; + } + + DWORD GetIndex() const { + return m_lightCount; + } + + bool IsActive() const { + return m_isActive; + } + + bool IsD3DLight2() const { + return m_isD3DLight2; + } + + private: + + bool m_isActive = false; + bool m_isD3DLight2 = false; + + static uint32_t s_lightCount; + uint32_t m_lightCount = 0; + + D3D6Viewport* m_viewport6 = nullptr; + D3D5Viewport* m_viewport5 = nullptr; + D3D3Viewport* m_viewport3 = nullptr; + + d3d9::D3DLIGHT9 m_light9 = { }; + + }; + +} diff --git a/src/ddraw/d3d_multithread.cpp b/src/ddraw/d3d_multithread.cpp new file mode 100644 index 00000000000..e64040da68b --- /dev/null +++ b/src/ddraw/d3d_multithread.cpp @@ -0,0 +1,12 @@ +#include "d3d7/d3d7_device.h" +#include "d3d6/d3d6_device.h" +#include "d3d5/d3d5_device.h" +#include "d3d3/d3d3_device.h" + +namespace dxvk { + + D3DMultithread::D3DMultithread( + BOOL Protected) + : m_protected( Protected ) { } + +} \ No newline at end of file diff --git a/src/ddraw/d3d_multithread.h b/src/ddraw/d3d_multithread.h new file mode 100644 index 00000000000..1ef2241203f --- /dev/null +++ b/src/ddraw/d3d_multithread.h @@ -0,0 +1,77 @@ +#pragma once + +#include "ddraw_include.h" + +namespace dxvk { + + /** + * \brief Device lock + * + * Lightweight RAII wrapper that implements + * a subset of the functionality provided by + * \c std::unique_lock, with the goal of being + * cheaper to construct and destroy. + */ + class D3DDeviceLock { + + public: + + D3DDeviceLock() + : m_mutex(nullptr) { } + + D3DDeviceLock(sync::RecursiveSpinlock& mutex) + : m_mutex(&mutex) { + mutex.lock(); + } + + D3DDeviceLock(D3DDeviceLock&& other) + : m_mutex(other.m_mutex) { + other.m_mutex = nullptr; + } + + D3DDeviceLock& operator = (D3DDeviceLock&& other) { + if (m_mutex) + m_mutex->unlock(); + + m_mutex = other.m_mutex; + other.m_mutex = nullptr; + return *this; + } + + ~D3DDeviceLock() { + if (m_mutex != nullptr) + m_mutex->unlock(); + } + + private: + + sync::RecursiveSpinlock* m_mutex; + + }; + + + /** + * \brief D3D context lock + */ + class D3DMultithread { + + public: + + D3DMultithread( + BOOL Protected); + + D3DDeviceLock AcquireLock() { + return m_protected + ? D3DDeviceLock(m_mutex) + : D3DDeviceLock(); + } + + private: + + BOOL m_protected; + + sync::RecursiveSpinlock m_mutex; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw.def b/src/ddraw/ddraw.def new file mode 100644 index 00000000000..5b7898c0356 --- /dev/null +++ b/src/ddraw/ddraw.def @@ -0,0 +1,23 @@ +LIBRARY DDRAW.DLL +EXPORTS + AcquireDDThreadLock @1 + CompleteCreateSysmemSurface @3 + D3DParseUnknownCommand @4 + DDGetAttachedSurfaceLcl @5 + DDInternalLock @6 + DDInternalUnlock @7 + DSoundHelp @8 + DirectDrawCreate @9 + DirectDrawCreateClipper @10 + DirectDrawCreateEx @11 + DirectDrawEnumerateA @12 + DirectDrawEnumerateExA @13 + DirectDrawEnumerateExW @14 + DirectDrawEnumerateW @15 + DllCanUnloadNow @16 + DllGetClassObject @17 + GetDDSurfaceLocal @18 + GetOLEThunkData @19 + GetSurfaceFromDC @20 + RegisterSpecialCase @21 + ReleaseDDThreadLock @22 diff --git a/src/ddraw/ddraw.sym b/src/ddraw/ddraw.sym new file mode 100644 index 00000000000..0a4799eea7f --- /dev/null +++ b/src/ddraw/ddraw.sym @@ -0,0 +1,27 @@ +{ + global: + AcquireDDThreadLock; + CompleteCreateSysmemSurface; + D3DParseUnknownCommand; + DDGetAttachedSurfaceLcl; + DDInternalLock; + DDInternalUnlock; + DSoundHelp; + DirectDrawCreate; + DirectDrawCreateClipper; + DirectDrawCreateEx; + DirectDrawEnumerateA; + DirectDrawEnumerateExA; + DirectDrawEnumerateExW; + DirectDrawEnumerateW; + DllCanUnloadNow; + DllGetClassObject; + GetDDSurfaceLocal; + GetOLEThunkData; + GetSurfaceFromDC; + RegisterSpecialCase; + ReleaseDDThreadLock; + + local: + *; +}; diff --git a/src/ddraw/ddraw/ddraw_interface.cpp b/src/ddraw/ddraw/ddraw_interface.cpp new file mode 100644 index 00000000000..8eeb13c2823 --- /dev/null +++ b/src/ddraw/ddraw/ddraw_interface.cpp @@ -0,0 +1,620 @@ +#include "ddraw_interface.h" + +#include "ddraw_surface.h" + +#include "../ddraw_clipper.h" +#include "../ddraw_palette.h" + +#include "../ddraw7/ddraw7_interface.h" +#include "../ddraw4/ddraw4_interface.h" +#include "../ddraw2/ddraw2_interface.h" + +#include "../d3d3/d3d3_interface.h" +#include "../d3d5/d3d5_interface.h" +#include "../d3d6/d3d6_interface.h" +#include + +namespace dxvk { + + uint32_t DDrawInterface::s_intfCount = 0; + + DDrawInterface::DDrawInterface( + DDrawCommonInterface* commonIntf, + Com&& proxyIntf) + : DDrawWrappedObject(nullptr, std::move(proxyIntf), nullptr) + , m_commonIntf ( commonIntf ) { + + if (m_commonIntf == nullptr) { + // We need a temporary D3D9 interface at this point to retrieve the options, + // even if we're only proxying and we don't yet have any child D3D interfaces + Com d3d9Intf = d3d9::Direct3DCreate9(D3D_SDK_VERSION); + Com d3d9Bridge; + + if (unlikely(FAILED(d3d9Intf->QueryInterface(__uuidof(IDxvkD3D8InterfaceBridge), reinterpret_cast(&d3d9Bridge))))) { + throw DxvkError("DDrawInterface: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!"); + } + + m_commonIntf = new DDrawCommonInterface(D3DOptions(*d3d9Bridge->GetConfig())); + } + + if (m_commonIntf->GetOrigin() == nullptr) + m_commonIntf->SetOrigin(this); + + m_commonIntf->SetDDInterface(this); + + static bool s_apitraceModeWarningShown; + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && + !std::exchange(s_apitraceModeWarningShown, true))) + Logger::warn("DDrawInterface: Apitrace mode is enabled. Performance will be suboptimal!"); + + m_intfCount = ++s_intfCount; + + Logger::debug(str::format("DDrawInterface: Created a new interface nr. <<1-", m_intfCount, ">>")); + } + + DDrawInterface::~DDrawInterface() { + if (m_commonIntf->GetOrigin() == this) + m_commonIntf->SetOrigin(nullptr); + + m_commonIntf->SetDDInterface(nullptr); + + Logger::debug(str::format("DDrawInterface: Interface nr. <<1-", m_intfCount, ">> bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDrawInterface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + // Standard way of retrieving a D3D6 interface + if (riid == __uuidof(IDirect3D3)) { + // GTA 2 queries for IDirect3D3 on IDirectDraw, after creating a + // IDirectDraw4 interface and a ton of surfaces... so forward the call + if (m_commonIntf->GetDD4Interface() != nullptr) { + Logger::debug("DDrawInterface::QueryInterface: Forwarded query for IDirect3D3"); + return m_commonIntf->GetDD4Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDrawInterface::QueryInterface: Query for IDirect3D3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + Com d3d6Intf = new D3D6Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + *ppvObject = d3d6Intf.ref(); + + return S_OK; + } + // Standard way of retrieving a D3D5 interface + if (unlikely(riid == __uuidof(IDirect3D2))) { + Logger::debug("DDrawInterface::QueryInterface: Query for IDirect3D2"); + + // Initialize the IDirect3D2 interlocked object + if (unlikely(m_d3d5Intf == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + m_d3d5Intf = new D3D5Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + } + + *ppvObject = m_d3d5Intf.ref(); + + return S_OK; + } + // Standard way of retrieving a D3D3 interface + if (unlikely(riid == __uuidof(IDirect3D))) { + Logger::debug("DDrawInterface::QueryInterface: Query for IDirect3D"); + + // Initialize the IDirect3D interlocked object + if (unlikely(m_d3d3Intf == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + m_d3d3Intf = new D3D3Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + m_commonIntf->SetD3D3Interface(m_d3d3Intf.ptr()); + } + + *ppvObject = m_d3d3Intf.ref(); + + return S_OK; + } + // Standard way of getting a DDraw4 interface + if (riid == __uuidof(IDirectDraw4)) { + if (m_commonIntf->GetDD4Interface() != nullptr) { + Logger::debug("DDrawInterface::QueryInterface: Query for existing IDirectDraw4"); + return m_commonIntf->GetDD4Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDrawInterface::QueryInterface: Query for IDirectDraw4"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw4Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Standard way of getting a DDraw2 interface + if (riid == __uuidof(IDirectDraw2)) { + if (m_commonIntf->GetDD2Interface() != nullptr) { + Logger::debug("DDrawInterface::QueryInterface: Query for existing IDirectDraw2"); + return m_commonIntf->GetDD2Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDrawInterface::QueryInterface: Query for IDirectDraw2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw2Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Legacy way of getting a DDraw7 interface + if (unlikely(riid == __uuidof(IDirectDraw7))) { + if (m_commonIntf->GetDD7Interface() != nullptr) { + Logger::debug("DDrawInterface::QueryInterface: Query for existing IDirectDraw7"); + return m_commonIntf->GetDD7Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDrawInterface::QueryInterface: Query for IDirectDraw7"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw7Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Quite a lot of games query for this IID during intro playback + if (unlikely(riid == GUID_IAMMediaStream)) { + Logger::debug("DDrawInterface::QueryInterface: Query for IAMMediaStream"); + return m_proxy->QueryInterface(riid, ppvObject); + } + // Also seen queried by some games, such as V-Rally 2: Expert Edition + if (unlikely(riid == GUID_IMediaStream)) { + Logger::debug("DDrawInterface::QueryInterface: Query for IMediaStream"); + return m_proxy->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // The documentation states: "The IDirectDraw::Compact method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDrawInterface::Compact() { + Logger::debug(">>> DDrawInterface::Compact"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::CreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) { + Logger::debug(">>> DDrawInterface::CreateClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + Com lplpDDClipperProxy; + HRESULT hr = m_proxy->CreateClipper(dwFlags, &lplpDDClipperProxy, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + *lplpDDClipper = ref(new DDrawClipper(std::move(lplpDDClipperProxy), this)); + } else { + Logger::warn("DDrawInterface::CreateClipper: Failed to create proxy clipper"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::CreatePalette(DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) { + Logger::debug(">>> DDrawInterface::CreatePalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + Com lplpDDPaletteProxy; + HRESULT hr = m_proxy->CreatePalette(dwFlags, lpColorTable, &lplpDDPaletteProxy, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + // Palettes created from IDirectDraw and IDirectDraw2 do not ref their parent interfaces + *lplpDDPalette = ref(new DDrawPalette(std::move(lplpDDPaletteProxy), nullptr)); + } else { + Logger::warn("DDrawInterface::CreatePalette: Failed to create proxy palette"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::CreateSurface(LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *pUnkOuter) { + Logger::debug(">>> DDrawInterface::CreateSurface"); + + // The cooperative level is always checked first + if (unlikely(!m_commonIntf->IsCooperativeLevelSet())) + return DDERR_NOCOOPERATIVELEVELSET; + + if (unlikely(lpDDSurfaceDesc == nullptr || lplpDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDSurface); + + // Because we are removing the DDSCAPS_WRITEONLY flag below, we need + // to first validate the combinations that would otherwise cause issues + HRESULT hr = ValidateSurfaceFlags(lpDDSurfaceDesc); + if (unlikely(FAILED(hr))) + return hr; + + // We need to ensure we can always read from surfaces for upload to + // D3D9, so always strip the DDSCAPS_WRITEONLY flag on creation + lpDDSurfaceDesc->ddsCaps.dwCaps &= ~DDSCAPS_WRITEONLY; + + if (unlikely((lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_ZBUFFER) + && (lpDDSurfaceDesc->ddpfPixelFormat.dwZBitMask == 0xFFFFFFFF))) { + if (m_commonIntf->GetOptions()->useD24X8forD32) { + // In case of up-front unsupported and unadvertised D32 depth stencil use, + // replace it with D24X8, as some games, such as Sacrifice, rely on it + // to properly enable 32-bit display modes (and revert to 16-bit otherwise) + Logger::info("DDrawInterface::CreateSurface: Using D24X8 instead of D32"); + lpDDSurfaceDesc->ddpfPixelFormat.dwZBitMask = 0xFFFFFF; + } else { + Logger::warn("DDrawInterface::CreateSurface: Use of unsupported D32"); + } + } + + Com ddrawSurfaceProxied; + hr = m_proxy->CreateSurface(lpDDSurfaceDesc, &ddrawSurfaceProxied, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + try{ + // Surfaces created from IDirectDraw and IDirectDraw2 do not ref their parent interfaces + Com surface = new DDrawSurface(nullptr, std::move(ddrawSurfaceProxied), this, nullptr, false); + if (unlikely(lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)) + m_commonIntf->SetPrimarySurface(surface->GetCommonSurface()); + *lplpDDSurface = surface.ref(); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } else { + Logger::debug("DDrawInterface::CreateSurface: Failed to create proxy surface"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::DuplicateSurface(LPDIRECTDRAWSURFACE lpDDSurface, LPDIRECTDRAWSURFACE *lplpDupDDSurface) { + Logger::debug("<<< DDrawInterface::DuplicateSurface: Proxy"); + + if (m_commonIntf->IsWrappedSurface(lpDDSurface)) { + InitReturnPtr(lplpDupDDSurface); + + DDrawSurface* ddrawSurface = static_cast(lpDDSurface); + Com dupSurface; + HRESULT hr = m_proxy->DuplicateSurface(ddrawSurface->GetProxied(), &dupSurface); + if (likely(SUCCEEDED(hr))) { + try { + *lplpDupDDSurface = ref(new DDrawSurface(nullptr, std::move(dupSurface), this, nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } + return hr; + } else { + if (unlikely(lpDDSurface != nullptr)) { + Logger::warn("DDrawInterface::DuplicateSurface: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + return m_proxy->DuplicateSurface(lpDDSurface, lplpDupDDSurface); + } + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::EnumDisplayModes(DWORD dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback) { + Logger::debug("<<< DDrawInterface::EnumDisplayModes: Proxy"); + return m_proxy->EnumDisplayModes(dwFlags, lpDDSurfaceDesc, lpContext, lpEnumModesCallback); + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::EnumSurfaces(DWORD dwFlags, LPDDSURFACEDESC lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) { + Logger::debug(">>> DDrawInterface::EnumSurfaces: Proxy"); + + if (unlikely(lpEnumSurfacesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + std::vector attachedSurfaces; + // Enumerate all surfaces from the underlying DDraw implementation + m_proxy->EnumSurfaces(dwFlags, lpDDSD, reinterpret_cast(&attachedSurfaces), EnumAttachedSurfacesCallback); + + HRESULT hr = DDENUMRET_OK; + + // Wrap surfaces as needed and perform the actual callback the application is requesting + auto surfaceIt = attachedSurfaces.begin(); + while (surfaceIt != attachedSurfaces.end() && hr == DDENUMRET_OK) { + Com surface = surfaceIt->surface; + + Com ddrawSurface = new DDrawSurface(nullptr, std::move(surface), this, nullptr, false); + hr = lpEnumSurfacesCallback(ddrawSurface.ref(), &surfaceIt->desc, lpContext); + + ++surfaceIt; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::FlipToGDISurface() { + Logger::debug("*** DDrawInterface::FlipToGDISurface: Ignoring"); + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) { + Logger::debug("<<< DDrawInterface::GetCaps: Proxy"); + + HRESULT hr = m_proxy->GetCaps(lpDDDriverCaps, lpDDHELCaps); + if (unlikely(FAILED(hr))) + return hr; + + static constexpr DWORD Megabytes = 1024 * 1024; + static constexpr DWORD MaxMemory = ddrawCaps::MaxTextureMemory * Megabytes; + static constexpr DWORD ReservedMemory = ddrawCaps::ReservedTextureMemory * Megabytes; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + // Properly fill in the dwVidMemTotal / dwVidMemFree fields + DWORD total9 = 0; + DWORD free9 = 0; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (likely(d3d9Device != nullptr)) { + Logger::debug("DDrawInterface::GetCaps: Getting memory stats from D3D9"); + + total9 = static_cast(m_commonIntf->GetTotalTextureMemory()); + free9 = static_cast(d3d9Device->GetAvailableTextureMem()); + + if (likely(total9 >= MaxMemory)) { + const DWORD delta = total9 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free9 > delta + ReservedMemory ? free9 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDrawInterface::GetCaps: Total: ", total9)); + Logger::debug(str::format("DDrawInterface::GetCaps: Free : ", free9)); + } else { + Logger::debug("DDrawInterface::GetCaps: Getting memory stats from DDraw"); + + const DWORD total3 = lpDDDriverCaps != nullptr ? lpDDDriverCaps->dwVidMemTotal : 0; + const DWORD free3 = lpDDDriverCaps != nullptr ? lpDDDriverCaps->dwVidMemFree : 0; + + Logger::debug(str::format("DDrawInterface::GetCaps: DDraw Total: ", total3)); + Logger::debug(str::format("DDrawInterface::GetCaps: DDraw Free : ", free3)); + + if (unlikely(total3 < MaxMemory)) { + total9 = total3; + free9 = free3; + } else { + const DWORD delta = total3 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free3 > delta + ReservedMemory ? free3 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDrawInterface::GetCaps: Total: ", total9)); + Logger::debug(str::format("DDrawInterface::GetCaps: Free : ", free9)); + } + + if (lpDDDriverCaps != nullptr) { + lpDDDriverCaps->dwZBufferBitDepths = d3dOptions->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + lpDDDriverCaps->dwVidMemTotal = total9; + lpDDDriverCaps->dwVidMemFree = free9; + lpDDDriverCaps->dwNumFourCCCodes = ddrawCaps::NumberOfFOURCCCodes; + } + if (lpDDHELCaps != nullptr) { + lpDDHELCaps->dwZBufferBitDepths = d3dOptions->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + lpDDHELCaps->dwVidMemTotal = total9; + lpDDHELCaps->dwVidMemFree = free9; + lpDDHELCaps->dwNumFourCCCodes = ddrawCaps::NumberOfFOURCCCodes; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::GetDisplayMode(LPDDSURFACEDESC lpDDSurfaceDesc) { + Logger::debug("<<< DDrawInterface::GetDisplayMode: Proxy"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + HRESULT hr = m_proxy->GetDisplayMode(lpDDSurfaceDesc); + if (unlikely(FAILED(hr))) + return hr; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->mask8BitModes && lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount == 8)) + lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount = 16; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::GetFourCCCodes(LPDWORD lpNumCodes, LPDWORD lpCodes) { + Logger::debug(">>> DDrawInterface::GetFourCCCodes"); + + if (likely(lpNumCodes != nullptr && lpCodes != nullptr)) { + const uint32_t copyNumCodes = std::min(ddrawCaps::NumberOfFOURCCCodes, *lpNumCodes); + for (uint32_t i = 0; i < copyNumCodes; i++) { + lpCodes[i] = ddrawCaps::SupportedFourCCs[i]; + } + } + + if (lpNumCodes != nullptr) + *lpNumCodes = ddrawCaps::NumberOfFOURCCCodes; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::GetGDISurface(LPDIRECTDRAWSURFACE *lplpGDIDDSurface) { + Logger::debug("<<< DDrawInterface::GetGDISurface: Proxy"); + + if(unlikely(lplpGDIDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpGDIDDSurface); + + Com gdiSurface; + HRESULT hr = m_proxy->GetGDISurface(&gdiSurface); + + if (unlikely(FAILED(hr))) { + Logger::debug("DDrawInterface::GetGDISurface: Failed to retrieve GDI surface"); + return hr; + } + + if (unlikely(m_commonIntf->IsWrappedSurface(gdiSurface.ptr()))) { + *lplpGDIDDSurface = gdiSurface.ref(); + } else { + Logger::debug("DDrawInterface::GetGDISurface: Received a non-wrapped GDI surface"); + try { + *lplpGDIDDSurface = ref(new DDrawSurface(nullptr, std::move(gdiSurface), this, nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::GetMonitorFrequency(LPDWORD lpdwFrequency) { + Logger::debug("<<< DDrawInterface::GetMonitorFrequency: Proxy"); + return m_proxy->GetMonitorFrequency(lpdwFrequency); + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::GetScanLine(LPDWORD lpdwScanLine) { + Logger::debug("<<< DDrawInterface::GetScanLine: Proxy"); + return m_proxy->GetScanLine(lpdwScanLine); + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::GetVerticalBlankStatus(LPBOOL lpbIsInVB) { + Logger::debug("<<< DDrawInterface::GetVerticalBlankStatus: Proxy"); + return m_proxy->GetVerticalBlankStatus(lpbIsInVB); + } + + // Should technically always return DDERR_ALREADYINITIALIZED, unless the + // interface is created via IClassFactory, however Requiem: Avenging Angel + // expects it to work on a regular interface too, after initially creating + // and releasing an interface through IClassFactory (but never initializing it). + // On native DDraw the initial interface most likely gets reused. In practice, + // applications that don't use IClassFactory won't call this, so keep it simple. + HRESULT STDMETHODCALLTYPE DDrawInterface::Initialize(GUID* lpGUID) { + Logger::debug(">>> DDrawInterface::Initialize"); + + if (unlikely(m_commonIntf->IsInitialized())) + return DDERR_ALREADYINITIALIZED; + + m_commonIntf->MarkAsInitialized(); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::RestoreDisplayMode() { + Logger::debug("<<< DDrawInterface::RestoreDisplayMode: Proxy"); + return m_proxy->RestoreDisplayMode(); + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::SetCooperativeLevel(HWND hWnd, DWORD dwFlags) { + Logger::debug("<<< DDrawInterface::SetCooperativeLevel: Proxy"); + + HRESULT hr = m_proxy->SetCooperativeLevel(hWnd, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + m_commonIntf->SetCooperativeLevel(hWnd, dwFlags); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP) { + Logger::debug("<<< DDrawInterface::SetDisplayMode: Proxy"); + + Logger::debug(str::format("DDrawInterface::SetDisplayMode: ", dwWidth, "x", dwHeight, ":", dwBPP)); + + HRESULT hr = m_proxy->SetDisplayMode(dwWidth, dwHeight, dwBPP); + if (unlikely(FAILED(hr))) + return hr; + + DDrawCommonSurface* ps = m_commonIntf->GetPrimarySurface(); + + if (likely(ps != nullptr)) { + hr = ps->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::warn("DDrawInterface::SetDisplayMode: Failed to update primary surface desc"); + } + + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent && + m_commonIntf->GetOptions()->backBufferResize)) { + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Ignore any mode size dimensions when in windowed present mode + if (exclusiveMode) { + Logger::debug("DDrawInterface::SetDisplayMode: Exclusive full-screen present mode in use"); + DDrawModeSize* modeSize = m_commonIntf->GetModeSize(); + if (modeSize->width != dwWidth || modeSize->height != dwHeight) { + modeSize->width = dwWidth; + modeSize->height = dwHeight; + } + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawInterface::WaitForVerticalBlank(DWORD dwFlags, HANDLE hEvent) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDrawInterface::WaitForVerticalBlank: Proxy"); + m_proxy->WaitForVerticalBlank(dwFlags, hEvent); + } + + Logger::debug(">>> DDrawInterface::WaitForVerticalBlank"); + + if (unlikely(dwFlags & DDWAITVB_BLOCKBEGINEVENT)) + return DDERR_UNSUPPORTED; + + // Switch to a default presentation interval when an application + // tries to wait for vertical blank, if we're not already doing so + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (unlikely(d3d9Device != nullptr && !m_commonIntf->GetWaitForVBlank())) { + Logger::info("DDrawInterface::WaitForVerticalBlank: Switching to D3DPRESENT_INTERVAL_DEFAULT for presentation"); + + d3d9::D3DPRESENT_PARAMETERS resetParams = m_commonIntf->GetPresentParameters(); + resetParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + HRESULT hrReset = m_commonIntf->ResetD3D9Swapchain(&resetParams); + if (likely(SUCCEEDED(hrReset))) + m_commonIntf->SetWaitForVBlank(true); + } + + return DD_OK; + } + +} diff --git a/src/ddraw/ddraw/ddraw_interface.h b/src/ddraw/ddraw/ddraw_interface.h new file mode 100644 index 00000000000..05ac089aed5 --- /dev/null +++ b/src/ddraw/ddraw/ddraw_interface.h @@ -0,0 +1,87 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_caps.h" + +#include "../ddraw_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +namespace dxvk { + + class D3D3Interface; + class D3D5Interface; + class DDrawSurface; + + /** + * \brief DirectDraw interface implementation + */ + class DDrawInterface final : public DDrawWrappedObject { + + public: + DDrawInterface( + DDrawCommonInterface* commonIntf, + Com&& proxyIntf); + + ~DDrawInterface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE Compact(); + + HRESULT STDMETHODCALLTYPE CreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreatePalette(DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateSurface(LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE DuplicateSurface(LPDIRECTDRAWSURFACE lpDDSurface, LPDIRECTDRAWSURFACE *lplpDupDDSurface); + + HRESULT STDMETHODCALLTYPE EnumDisplayModes(DWORD dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback); + + HRESULT STDMETHODCALLTYPE EnumSurfaces(DWORD dwFlags, LPDDSURFACEDESC lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE FlipToGDISurface(); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps); + + HRESULT STDMETHODCALLTYPE GetDisplayMode(LPDDSURFACEDESC lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE GetFourCCCodes(LPDWORD lpNumCodes, LPDWORD lpCodes); + + HRESULT STDMETHODCALLTYPE GetGDISurface(LPDIRECTDRAWSURFACE *lplpGDIDDSurface); + + HRESULT STDMETHODCALLTYPE GetMonitorFrequency(LPDWORD lpdwFrequency); + + HRESULT STDMETHODCALLTYPE GetScanLine(LPDWORD lpdwScanLine); + + HRESULT STDMETHODCALLTYPE GetVerticalBlankStatus(LPBOOL lpbIsInVB); + + HRESULT STDMETHODCALLTYPE Initialize(GUID* lpGUID); + + HRESULT STDMETHODCALLTYPE RestoreDisplayMode(); + + HRESULT STDMETHODCALLTYPE SetCooperativeLevel(HWND hWnd, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP); + + HRESULT STDMETHODCALLTYPE WaitForVerticalBlank(DWORD dwFlags, HANDLE hEvent); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf.ptr(); + } + + private: + + static uint32_t s_intfCount; + uint32_t m_intfCount = 0; + + Com m_commonIntf; + + Com m_d3d3Intf; + Com m_d3d5Intf; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw/ddraw_surface.cpp b/src/ddraw/ddraw/ddraw_surface.cpp new file mode 100644 index 00000000000..25e085675fc --- /dev/null +++ b/src/ddraw/ddraw/ddraw_surface.cpp @@ -0,0 +1,1609 @@ +#include "ddraw_surface.h" + +#include "../ddraw_gamma.h" + +#include "../d3d3/d3d3_device.h" +#include "../d3d3/d3d3_interface.h" +#include "../d3d3/d3d3_texture.h" + +#include "../ddraw2/ddraw2_surface.h" +#include "../ddraw2/ddraw3_surface.h" +#include "../ddraw4/ddraw4_surface.h" +#include "../ddraw7/ddraw7_surface.h" +#include + +namespace dxvk { + + uint32_t DDrawSurface::s_surfCount = 0; + + DDrawSurface::DDrawSurface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDrawInterface* pParent, + DDrawSurface* pParentSurf, + bool isChildObject) + : DDrawWrappedObject(pParent, std::move(surfProxy), nullptr) + , m_isChildObject ( isChildObject ) + , m_commonSurf ( commonSurf ) + , m_parentSurf ( pParentSurf ) { + if (m_parent != nullptr) { + m_commonIntf = m_parent->GetCommonInterface(); + } else if (m_parentSurf != nullptr) { + m_commonIntf = m_parentSurf->GetCommonInterface(); + } else if (m_commonSurf != nullptr) { + m_commonIntf = m_commonSurf->GetCommonInterface(); + } else { + throw DxvkError("DDrawSurface: ERROR! Failed to retrieve the common interface!"); + } + + if (m_commonSurf == nullptr) + m_commonSurf = new DDrawCommonSurface(m_commonIntf); + + // Retrieve and cache the proxy surface desc + if (!m_commonSurf->IsDescSet()) { + DDSURFACEDESC desc; + desc.dwSize = sizeof(DDSURFACEDESC); + HRESULT hr = m_proxy->GetSurfaceDesc(&desc); + + if (unlikely(FAILED(hr))) { + throw DxvkError("DDrawSurface: ERROR! Failed to retrieve new surface desc!"); + } else { + m_commonSurf->SetDesc(desc); + } + } + + m_commonIntf->AddWrappedSurface(this); + + m_commonSurf->SetDDSurface(this); + + if (m_parentSurf != nullptr && !m_commonSurf->IsFrontBuffer() + && m_parentSurf->GetCommonSurface()->IsBackBufferOrFlippable() + && !m_commonIntf->GetOptions()->forceSingleBackBuffer) { + const uint32_t index = m_parentSurf->GetCommonSurface()->GetBackBufferIndex(); + m_commonSurf->IncrementBackBufferIndex(index); + } + + if (m_parent != nullptr && m_isChildObject) + m_parent->AddRef(); + + m_surfCount = ++s_surfCount; + + Logger::debug(str::format("DDrawSurface: Created a new surface nr. [[1-", m_surfCount, "]]")); + + if (m_commonSurf->GetOrigin() == nullptr) { + m_commonSurf->SetOrigin(this); + m_commonSurf->SetIsAttached(m_parentSurf != nullptr); + m_commonSurf->ListSurfaceDetails(); + } + } + + DDrawSurface::~DDrawSurface() { + if (m_commonSurf->GetOrigin() == this) + m_commonSurf->SetOrigin(nullptr); + + // Clear the cached depth stencil on the parent if matched + if (unlikely(m_parentSurf != nullptr && m_commonSurf->IsDepthStencil() + && m_parentSurf->GetAttachedDepthStencil() == this)) { + m_parentSurf->ClearAttachedDepthStencil(); + } + + m_commonIntf->RemoveWrappedSurface(this); + + // Release all public references on all attached surfaces + for (auto & attachedSurface : m_attachedSurfaces) { + uint32_t attachedRef; + do { + attachedRef = attachedSurface.second->Release(); + } while (attachedRef > 0); + } + + if (m_parent != nullptr && m_isChildObject) + m_parent->Release(); + + m_commonSurf->SetDDSurface(nullptr); + + Logger::debug(str::format("DDrawSurface: Surface nr. [[1-", m_surfCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDrawSurface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(riid == __uuidof(IDirect3DTexture))) { + Logger::debug("DDrawSurface::QueryInterface: Query for IDirect3DTexture"); + + if (unlikely(m_texture3 == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + D3DTEXTUREHANDLE nextHandle = m_commonIntf->GetNextTextureHandle(); + m_texture3 = new D3D3Texture(std::move(ppvProxyObject), this, nextHandle); + D3DCommonTexture* commonTex = m_texture3->GetCommonTexture(); + m_commonIntf->EmplaceTexture(commonTex, nextHandle); + } + + *ppvObject = m_texture3.ref(); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirect3DTexture2))) { + Logger::debug("DDrawSurface::QueryInterface: Query for IDirect3DTexture2"); + + if (unlikely(m_texture5 == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + D3DTEXTUREHANDLE nextHandle = m_commonIntf->GetNextTextureHandle(); + m_texture5 = new D3D5Texture(std::move(ppvProxyObject), this, nextHandle); + D3DCommonTexture* commonTex = m_texture5->GetCommonTexture(); + m_commonIntf->EmplaceTexture(commonTex, nextHandle); + } + + *ppvObject = m_texture5.ref(); + + return S_OK; + } + // Wrap IDirectDrawGammaControl, to potentially ignore application set gamma ramps + if (riid == __uuidof(IDirectDrawGammaControl)) { + Logger::debug("DDrawSurface::QueryInterface: Query for IDirectDrawGammaControl"); + void* gammaControlProxiedVoid = nullptr; + // This can never reasonably fail + m_proxy->QueryInterface(__uuidof(IDirectDrawGammaControl), &gammaControlProxiedVoid); + Com gammaControlProxied = static_cast(gammaControlProxiedVoid); + *ppvObject = ref(new DDrawGammaControl(m_commonSurf.ptr(), std::move(gammaControlProxied), this)); + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawColorControl))) { + Logger::debug("DDrawSurface::QueryInterface: Query for IDirectDrawColorControl"); + return E_NOINTERFACE; + } + // The standard way of creating a new D3D3 device. Outside of RAMP, MMX, RGB and HAL, + // some applications (e.g. Dark Rift) query for Wine's advertised custom device IID. + if (riid == IID_IDirect3DHALDevice || riid == IID_IDirect3DRGBDevice || + riid == IID_IDirect3DMMXDevice || riid == IID_IDirect3DRampDevice || + riid == IID_WineD3DDevice) { + Logger::debug("DDrawSurface::QueryInterface: Query with an IDirect3DDevice IID"); + + HRESULT hr = CreateDeviceInternal(riid, ppvObject); + if (unlikely(FAILED(hr))) + return E_NOINTERFACE; + + return S_OK; + } + // Some applications check the supported API level by querying the various newer surface GUIDs... + if (unlikely(riid == __uuidof(IDirectDrawSurface2))) { + if (m_commonSurf->GetDD2Surface() != nullptr) { + Logger::debug("DDrawSurface::QueryInterface: Query for existing IDirectDrawSurface2"); + return m_commonSurf->GetDD2Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDrawSurface::QueryInterface: Query for IDirectDrawSurface2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw2Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), this, nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface3))) { + if (m_commonSurf->GetDD3Surface() != nullptr) { + Logger::debug("DDrawSurface::QueryInterface: Query for existing IDirectDrawSurface3"); + return m_commonSurf->GetDD3Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDrawSurface::QueryInterface: Query for IDirectDrawSurface3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw3Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), this, nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface4))) { + if (m_commonSurf->GetDD4Surface() != nullptr) { + Logger::debug("DDrawSurface::QueryInterface: Query for existing IDirectDrawSurface4"); + return m_commonSurf->GetDD4Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDrawSurface::QueryInterface: Query for IDirectDrawSurface4"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw4Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDD4Interface(), nullptr, false)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface7))) { + if (m_commonSurf->GetDD7Surface() != nullptr) { + Logger::debug("DDrawSurface::QueryInterface: Query for existing IDirectDrawSurface7"); + return m_commonSurf->GetDD7Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDrawSurface::QueryInterface: Query for IDirectDrawSurface7"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw7Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDD7Interface(), nullptr, false)); + + return S_OK; + } + // Some games are known to query the clipper from the surface, + // though that won't work and GetClipper exists anyway... + if (unlikely(riid == __uuidof(IDirectDrawClipper))) { + Logger::debug("DDrawSurface::QueryInterface: Query for IDirectDrawClipper"); + return E_NOINTERFACE; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::AddAttachedSurface(LPDIRECTDRAWSURFACE lpDDSAttachedSurface) { + Logger::debug("<<< DDrawSurface::AddAttachedSurface: Proxy"); + + if (unlikely(lpDDSAttachedSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + Logger::warn("DDrawSurface::AddAttachedSurface: Received an unwrapped surface"); + return DDERR_CANNOTATTACHSURFACE; + } + + DDrawSurface* ddrawSurface = static_cast(lpDDSAttachedSurface); + + if (unlikely(ddrawSurface->GetCommonSurface()->IsBackBufferOrFlippable())) + Logger::warn("DDrawSurface::AddAttachedSurface: Trying to attach a flippable surface"); + + HRESULT hr = m_proxy->AddAttachedSurface(ddrawSurface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + ddrawSurface->SetParentSurface(this); + if (likely(ddrawSurface->GetCommonSurface()->IsDepthStencil())) + m_depthStencil = ddrawSurface; + + return hr; + } + + // Docs: "This method is used for the software implementation. + // It is not needed if the overlay support is provided by the hardware." + HRESULT STDMETHODCALLTYPE DDrawSurface::AddOverlayDirtyRect(LPRECT lpRect) { + Logger::debug(">>> DDrawSurface::AddOverlayDirtyRect"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) { + Logger::debug("<<< DDrawSurface::Blt: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDrawSurface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDrawSurface::Blt: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDrawSurface::Blt: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDrawSurface::Blt: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDrawSurface::Blt: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + // Forward DDBLT_DEPTHFILL clears to D3D9 if done on the current depth stencil + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_DEPTHFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9DepthStencil(m_d3d9.ptr()))) { + Logger::debug("DDrawSurface::Blt: Clearing d3d9 depth stencil"); + + HRESULT hrClear; + const float zClear = m_commonSurf->GetNormalizedFloatDepth(lpDDBltFx->dwFillDepth); + + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0, zClear, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_ZBUFFER, 0, zClear, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDrawSurface::Blt: Failed to clear d3d9 depth"); + } + // Forward DDBLT_COLORFILL clears to D3D9 if done on the current render target + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_COLORFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9RenderTarget(m_d3d9.ptr()))) { + Logger::debug("DDrawSurface::Blt: Clearing d3d9 render target"); + + HRESULT hrClear; + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDrawSurface::Blt: Failed to clear d3d9 render target"); + } + + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDrawSurface::Blt: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->Blt(lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFx); + } else { + DDrawSurface* ddrawSurface = static_cast(lpDDSrcSurface); + hr = m_proxy->Blt(lpDestRect, ddrawSurface->GetProxied(), lpSrcRect, dwFlags, lpDDBltFx); + } + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDrawSurface::Blt: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + // Docs: "The IDirectDrawSurface::BltBatch method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDrawSurface::BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) { + Logger::debug("<<< DDrawSurface::BltBatch: Proxy"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) { + Logger::debug("<<< DDrawSurface::BltFast: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDrawSurface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDrawSurface::BltFast: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDrawSurface::BltFast: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDrawSurface::BltFast: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDrawSurface::BltFast: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDrawSurface::BltFast: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->BltFast(dwX, dwY, lpDDSrcSurface, lpSrcRect, dwTrans); + } else { + DDrawSurface* ddrawSurface = static_cast(lpDDSrcSurface); + hr = m_proxy->BltFast(dwX, dwY, ddrawSurface->GetProxied(), lpSrcRect, dwTrans); + } + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDrawSurface::BltFast: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE lpDDSAttachedSurface) { + Logger::debug("<<< DDrawSurface::DeleteAttachedSurface: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + if (unlikely(lpDDSAttachedSurface != nullptr)) { + Logger::warn("DDrawSurface::DeleteAttachedSurface: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + HRESULT hrProxy = m_proxy->DeleteAttachedSurface(dwFlags, lpDDSAttachedSurface); + + // If lpDDSAttachedSurface is NULL, then all surfaces are detached + if (lpDDSAttachedSurface == nullptr && likely(SUCCEEDED(hrProxy))) + m_depthStencil = nullptr; + + return hrProxy; + } + + DDrawSurface* ddrawSurface = static_cast(lpDDSAttachedSurface); + + HRESULT hr = m_proxy->DeleteAttachedSurface(dwFlags, ddrawSurface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + if (likely(m_depthStencil == ddrawSurface)) { + ddrawSurface->ClearParentSurface(); + m_depthStencil = nullptr; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) { + Logger::debug(">>> DDrawSurface::EnumAttachedSurfaces"); + + if (unlikely(lpEnumSurfacesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + std::vector attachedSurfaces; + // Enumerate all attached surfaces from the underlying DDraw implementation + m_proxy->EnumAttachedSurfaces(reinterpret_cast(&attachedSurfaces), EnumAttachedSurfacesCallback); + + HRESULT hr = DDENUMRET_OK; + + // Wrap surfaces as needed and perform the actual callback the application is requesting + auto surfaceIt = attachedSurfaces.begin(); + while (surfaceIt != attachedSurfaces.end() && hr == DDENUMRET_OK) { + Com surface = surfaceIt->surface; + + auto attachedSurfaceIter = m_attachedSurfaces.find(surface.ptr()); + if (unlikely(attachedSurfaceIter == m_attachedSurfaces.end())) { + // Return the already attached depth surface if it exists + if (unlikely(m_depthStencil != nullptr && surface.ptr() == m_depthStencil->GetProxied())) { + hr = lpEnumSurfacesCallback(m_depthStencil.ref(), &surfaceIt->desc, lpContext); + } else { + Com ddrawSurface = new DDrawSurface(nullptr, std::move(surface), m_parent, this, false); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(ddrawSurface->GetProxied()), + std::forward_as_tuple(ddrawSurface.ref())); + hr = lpEnumSurfacesCallback(ddrawSurface.ref(), &surfaceIt->desc, lpContext); + } + } else { + hr = lpEnumSurfacesCallback(attachedSurfaceIter->second.ref(), &surfaceIt->desc, lpContext); + } + + ++surfaceIt; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback) { + Logger::debug("<<< DDrawSurface::EnumOverlayZOrders: Proxy"); + return m_proxy->EnumOverlayZOrders(dwFlags, lpContext, lpfnCallback); + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::Flip(LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride, DWORD dwFlags) { + // Lost surfaces are not flippable + HRESULT hr = m_proxy->IsLost(); + if (unlikely(FAILED(hr))) { + Logger::debug("DDrawSurface::Flip: Lost surface"); + return hr; + } + + if (unlikely(!(m_commonSurf->IsFrontBuffer() || m_commonSurf->IsBackBufferOrFlippable()))) { + Logger::debug("DDrawSurface::Flip: Unflippable surface"); + return DDERR_NOTFLIPPABLE; + } + + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Non-exclusive mode validations + if (unlikely(m_commonSurf->IsPrimarySurface() && !exclusiveMode)) { + Logger::debug("DDrawSurface::Flip: Primary surface flip in non-exclusive mode"); + return DDERR_NOEXCLUSIVEMODE; + } + + // Exclusive mode validations + if (unlikely(m_commonSurf->IsBackBufferOrFlippable() && exclusiveMode)) { + Logger::debug("DDrawSurface::Flip: Back buffer flip in exclusive mode"); + return DDERR_NOTFLIPPABLE; + } + + Com surf; + if (m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride)) { + surf = static_cast(lpDDSurfaceTargetOverride); + + if (unlikely(!surf->GetCommonSurface()->IsBackBufferOrFlippable())) { + Logger::debug("DDrawSurface::Flip: Unflippable override surface"); + return DDERR_NOTFLIPPABLE; + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + Logger::debug("*** DDrawSurface::Flip: Presenting"); + + m_commonIntf->ResetDrawTracking(); + + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(!IsInitialized())) + InitializeD3D9(m_commonIntf->IsCurrentRenderTarget(this)); + + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride))) { + if (unlikely(lpDDSurfaceTargetOverride != nullptr)) { + Logger::warn("DDrawSurface::Flip: Received an unwrapped surface"); + return DDERR_GENERIC; + } + if (likely(m_commonIntf->IsCurrentRenderTarget(this))) + m_commonIntf->SetFlipRTSurfaceAndFlags(lpDDSurfaceTargetOverride, dwFlags); + return m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } else { + if (likely(m_commonIntf->IsCurrentRenderTarget(this))) + m_commonIntf->SetFlipRTSurfaceAndFlags(lpDDSurfaceTargetOverride, dwFlags); + return m_proxy->Flip(surf->GetProxied(), dwFlags); + } + } + + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + // If we don't have a valid D3D5 device, this means a D3D3 application + // is trying to flip the surface. Allow that for compatibility reasons. + } else { + Logger::debug("<<< DDrawSurface::Flip: Proxy"); + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride))) { + m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } else { + m_proxy->Flip(surf->GetProxied(), dwFlags); + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetAttachedSurface(LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE *lplpDDAttachedSurface) { + Logger::debug("<<< DDrawSurface::GetAttachedSurface: Proxy"); + + if (unlikely(lpDDSCaps == nullptr || lplpDDAttachedSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (lpDDSCaps->dwCaps & DDSCAPS_PRIMARYSURFACE) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for the primary surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FRONTBUFFER) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for the front buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_BACKBUFFER) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for the back buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FLIP) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for a flippable surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OFFSCREENPLAIN) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for an offscreen plain surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_ZBUFFER) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for a depth stencil"); + else if (lpDDSCaps->dwCaps & DDSCAPS_MIPMAP) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for a texture mip map"); + else if (lpDDSCaps->dwCaps & DDSCAPS_TEXTURE) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for a texture"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OVERLAY) + Logger::debug("DDrawSurface::GetAttachedSurface: Querying for an overlay"); + + Com surface; + HRESULT hr = m_proxy->GetAttachedSurface(lpDDSCaps, &surface); + + // These are rather common, as some games query expecting to get nothing in return, for + // example it's a common use case to query the mip attach chain until nothing is returned + if (FAILED(hr)) { + Logger::debug("DDrawSurface::GetAttachedSurface: Failed to find the requested surface"); + *lplpDDAttachedSurface = surface.ptr(); + return hr; + } + + try { + auto attachedSurfaceIter = m_attachedSurfaces.find(surface.ptr()); + if (unlikely(attachedSurfaceIter == m_attachedSurfaces.end())) { + // Return the already attached depth surface if it exists + if (unlikely(m_depthStencil != nullptr && surface.ptr() == m_depthStencil->GetProxied())) { + *lplpDDAttachedSurface = m_depthStencil.ref(); + } else { + Com ddrawSurface = new DDrawSurface(nullptr, std::move(surface), m_parent, this, false); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(ddrawSurface->GetProxied()), + std::forward_as_tuple(ddrawSurface.ref())); + *lplpDDAttachedSurface = ddrawSurface.ref(); + } + } else { + *lplpDDAttachedSurface = attachedSurfaceIter->second.ref(); + } + } catch (const DxvkError& e) { + Logger::err(e.message()); + *lplpDDAttachedSurface = nullptr; + return DDERR_GENERIC; + } + + return DD_OK; + } + + // Blitting can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDrawSurface::GetBltStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDrawSurface::GetBltStatus: Proxy"); + m_proxy->GetBltStatus(dwFlags); + } + + Logger::debug(">>> DDrawSurface::GetBltStatus"); + + if (likely(dwFlags == DDGBS_CANBLT || dwFlags == DDGBS_ISBLTDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetCaps(LPDDSCAPS lpDDSCaps) { + Logger::debug(">>> DDrawSurface::GetCaps"); + + if (unlikely(lpDDSCaps == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDSCaps = m_commonSurf->GetDesc()->ddsCaps; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper) { + Logger::debug(">>> DDrawSurface::GetClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + DDrawClipper* clipper = m_commonSurf->GetClipper(); + + if (unlikely(clipper == nullptr)) + return DDERR_NOCLIPPERATTACHED; + + *lplpDDClipper = ref(clipper); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDrawSurface::GetColorKey: Proxy"); + return m_proxy->GetColorKey(dwFlags, lpDDColorKey); + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetDC(HDC *lphDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(lphDC == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lphDC); + + // Foward GetDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDrawSurface::GetDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDrawSurface::GetDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_d3d9->GetDC(lphDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDrawSurface::GetDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDrawSurface::GetDC: Proxy"); + return m_proxy->GetDC(lphDC); + } + + // Flipping can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDrawSurface::GetFlipStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDrawSurface::GetFlipStatus: Proxy"); + m_proxy->GetFlipStatus(dwFlags); + } + + Logger::debug(">>> DDrawSurface::GetFlipStatus"); + + if (likely(dwFlags == DDGFS_CANFLIP || dwFlags == DDGFS_ISFLIPDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetOverlayPosition(LPLONG lplX, LPLONG lplY) { + Logger::debug("<<< DDrawSurface::GetOverlayPosition: Proxy"); + return m_proxy->GetOverlayPosition(lplX, lplY); + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette) { + Logger::debug(">>> DDrawSurface::GetPalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + DDrawPalette* palette = m_commonSurf->GetPalette(); + + if (unlikely(palette == nullptr)) + return DDERR_NOPALETTEATTACHED; + + *lplpDDPalette = ref(palette); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat) { + Logger::debug(">>> DDrawSurface::GetPixelFormat"); + + if (unlikely(lpDDPixelFormat == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDPixelFormat = m_commonSurf->GetDesc()->ddpfPixelFormat; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc) { + Logger::debug(">>> DDrawSurface::GetSurfaceDesc"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpDDSurfaceDesc->dwSize != sizeof(DDSURFACEDESC))) + return DDERR_INVALIDPARAMS; + + *lpDDSurfaceDesc = *m_commonSurf->GetDesc(); + + return DD_OK; + } + + // According to the docs: "Because the DirectDrawSurface object is initialized + // when it's created, this method always returns DDERR_ALREADYINITIALIZED." + HRESULT STDMETHODCALLTYPE DDrawSurface::Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc) { + Logger::debug(">>> DDrawSurface::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::IsLost() { + Logger::debug("<<< DDrawSurface::IsLost: Proxy"); + return m_proxy->IsLost(); + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::Lock(LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) { + Logger::debug("<<< DDrawSurface::Lock: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (unlikely(m_commonSurf->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDrawSurface::Lock: Surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !IsInitialized())) + InitializeOrUploadD3D9(); + + if (likely(IsInitialized())) + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDrawSurface::Lock: Surface is a swapchain surface"); + } + } else if (unlikely(m_commonSurf->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDrawSurface::Lock: Surface is a depth stencil"); + + if (likely(IsInitialized())) + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDrawSurface::Lock: Surface is a depth stencil"); + } + } + + return m_proxy->Lock(lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent); + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::ReleaseDC(HDC hDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + // Foward ReleaseDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDrawSurface::ReleaseDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDrawSurface::ReleaseDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_d3d9->ReleaseDC(hDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDrawSurface::ReleaseDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDrawSurface::ReleaseDC: Proxy"); + + HRESULT hr = m_proxy->ReleaseDC(hDC); + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (m_commonSurf->IsTexture()) { + m_commonSurf->DirtyMipMaps(); + } else if (unlikely(m_commonIntf->GetOptions()->apitraceMode)) { + // We should ideally upload the surface contents here at all times, + // however some games are amazing, and do hundreds of locks on the same + // surface per frame, so this would absolutely tank performance + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDrawSurface::ReleaseDC: Failed upload to d3d9 surface"); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::Restore() { + Logger::debug("<<< DDrawSurface::Restore: Proxy"); + return m_proxy->Restore(); + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper) { + Logger::debug("<<< DDrawSurface::SetClipper: Proxy"); + + // A nullptr lpDDClipper gets the current clipper detached + if (lpDDClipper == nullptr) { + HRESULT hr = m_proxy->SetClipper(lpDDClipper); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(nullptr); + } else { + DDrawClipper* ddrawClipper = static_cast(lpDDClipper); + + HRESULT hr = m_proxy->SetClipper(ddrawClipper->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(ddrawClipper); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDrawSurface::SetColorKey: Proxy"); + + HRESULT hr = m_proxy->SetColorKey(dwFlags, lpDDColorKey); + if (unlikely(FAILED(hr))) + return hr; + + hr = m_commonSurf->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::err("DDrawSurface::SetColorKey: Failed to retrieve updated surface desc"); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::SetOverlayPosition(LONG lX, LONG lY) { + Logger::debug("<<< DDrawSurface::SetOverlayPosition: Proxy"); + return m_proxy->SetOverlayPosition(lX, lY); + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) { + Logger::debug("<<< DDrawSurface::SetPalette: Proxy"); + + // A nullptr lpDDPalette gets the current palette detached + if (lpDDPalette == nullptr) { + HRESULT hr = m_proxy->SetPalette(lpDDPalette); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(nullptr); + } else { + DDrawPalette* ddrawPalette = static_cast(lpDDPalette); + + HRESULT hr = m_proxy->SetPalette(ddrawPalette->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(ddrawPalette); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::Unlock(LPVOID lpSurfaceData) { + Logger::debug("<<< DDrawSurface::Unlock: Proxy"); + + HRESULT hr = m_proxy->Unlock(lpSurfaceData); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDrawSurface::Unlock: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) { + Logger::debug("<<< DDrawSurface::UpdateOverlay: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDDestSurface))) { + Logger::warn("DDrawSurface::UpdateOverlay: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDrawSurface* ddrawSurface = static_cast(lpDDDestSurface); + return m_proxy->UpdateOverlay(lpSrcRect, ddrawSurface->GetProxied(), lpDestRect, dwFlags, lpDDOverlayFx); + } + + // Docs: "This method is for software emulation only; it does nothing if the hardware supports overlays." + HRESULT STDMETHODCALLTYPE DDrawSurface::UpdateOverlayDisplay(DWORD dwFlags) { + Logger::debug(">>> DDrawSurface::UpdateOverlayDisplay"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDrawSurface::UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE lpDDSReference) { + Logger::debug("<<< DDrawSurface::UpdateOverlayZOrder: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSReference))) { + Logger::warn("DDrawSurface::UpdateOverlayZOrder: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDrawSurface* ddrawSurface = static_cast(lpDDSReference); + return m_proxy->UpdateOverlayZOrder(dwFlags, ddrawSurface->GetProxied()); + } + + HRESULT DDrawSurface::InitializeD3D9RenderTarget() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (unlikely(!IsInitialized())) + hr = InitializeD3D9(true); + + return hr; + } + + HRESULT DDrawSurface::InitializeD3D9DepthStencil() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (unlikely(!IsInitialized())) + hr = InitializeD3D9(false); + + return hr; + } + + HRESULT DDrawSurface::InitializeOrUploadD3D9() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (likely(IsInitialized())) { + hr = UploadSurfaceData(); + } else { + hr = InitializeD3D9(false); + } + + return hr; + } + + HRESULT DDrawSurface::InitializeD3D9(const bool initRT) { + if (unlikely(m_d3d9Device == nullptr)) { + Logger::debug("DDrawSurface::InitializeD3D9: Null device, can't initialize right now"); + return DD_OK; + } + + Logger::debug(str::format("DDrawSurface::InitializeD3D9: Initializing nr. [[1-", m_surfCount, "]]")); + + const DDSURFACEDESC* desc = m_commonSurf->GetDesc(); + const d3d9::D3DFORMAT format = m_commonSurf->GetD3D9Format(); + + if (unlikely(desc->dwHeight == 0 || desc->dwWidth == 0)) { + Logger::err("DDrawSurface::InitializeD3D9: Surface has 0 height or width"); + return DDERR_GENERIC; + } + + if (unlikely(format == d3d9::D3DFMT_UNKNOWN)) { + Logger::err("DDrawSurface::InitializeD3D9: Surface has an unknown format"); + return DDERR_GENERIC; + } + + // Don't initialize P8 textures/surfaces since we don't support them. + // Some applications do require them to be created by ddraw, otherwise + // they will simply fail to start, so just ignore them for now. + if (unlikely(format == d3d9::D3DFMT_P8)) { + static bool s_formatP8ErrorShown; + + if (!std::exchange(s_formatP8ErrorShown, true)) + Logger::warn("DDrawSurface::InitializeD3D9: Unsupported format D3DFMT_P8"); + + return DD_OK; + + // Similarly, D3DFMT_R3G3B2 isn't supported by D3D9 dxvk, however some + // applications require it to be supported by ddraw, even if they do not + // use it. Simply ignore any D3DFMT_R3G3B2 textures/surfaces for now. + } else if (unlikely(format == d3d9::D3DFMT_R3G3B2)) { + static bool s_formatR3G3B2ErrorShown; + + if (!std::exchange(s_formatR3G3B2ErrorShown, true)) + Logger::warn("DDrawSurface::InitializeD3D9: Unsupported format D3DFMT_R3G3B2"); + + return DD_OK; + } + + // We need to count the number of actual mips on initialization by going through + // the mip chain, since the dwMipMapCount number may or may not be accurate. I am + // guessing it was intended more as a hint, not neceesarily a set number. + if (m_commonSurf->IsTexture()) { + IDirectDrawSurface* mipMap = m_proxy.ptr(); + DDSURFACEDESC mipDesc; + uint16_t mipCount = 1; + + while (mipMap != nullptr) { + IDirectDrawSurface* parentSurface = mipMap; + mipMap = nullptr; + parentSurface->EnumAttachedSurfaces(&mipMap, ListMipChainSurfacesCallback); + if (mipMap != nullptr) { + mipCount++; + + mipDesc = { }; + mipDesc.dwSize = sizeof(DDSURFACEDESC2); + mipMap->GetSurfaceDesc(&mipDesc); + // Ignore multiple 1x1 mips, which apparently can get generated if the + // application gets the dwMipMapCount wrong vs surface dimensions. + if (unlikely(mipDesc.dwWidth == 1 && mipDesc.dwHeight == 1)) + break; + } + } + + // Do not worry about maximum supported mip map levels validation, + // because D3D9 will handle this for us and cap them appropriately + if (mipCount > 1) { + Logger::debug(str::format("DDrawSurface::InitializeD3D9: Found ", mipCount, " mip levels")); + + if (unlikely(mipCount != desc->dwMipMapCount)) + Logger::debug(str::format("DDrawSurface::InitializeD3D9: Mismatch with declared ", desc->dwMipMapCount, " mip levels")); + } + + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) { + Logger::debug("DDrawSurface::InitializeD3D9: Using auto mip map generation"); + mipCount = 0; + } + + m_commonSurf->SetMipCount(mipCount); + } + + d3d9::D3DPOOL pool = d3d9::D3DPOOL_DEFAULT; + DWORD usage = 0; + + // General surface/texture pool placement + if (desc->ddsCaps.dwCaps & DDSCAPS_LOCALVIDMEM) + pool = d3d9::D3DPOOL_DEFAULT; + // There's no explicit non-local video memory placement + // per se, but D3DPOOL_MANAGED is close enough + else if (desc->ddsCaps.dwCaps & DDSCAPS_NONLOCALVIDMEM) + pool = d3d9::D3DPOOL_MANAGED; + else if (desc->ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY) + // We can't know beforehand if a texture is or isn't going to be + // used in SetTexture() calls, and textures placed in D3DPOOL_SYSTEMMEM + // will not work in that context in dxvk, so revert to D3DPOOL_MANAGED. + pool = m_commonSurf->IsTexture() ? d3d9::D3DPOOL_MANAGED : d3d9::D3DPOOL_SYSTEMMEM; + + // Place all possible render targets in DEFAULT + // + // Note: This is somewhat problematic for textures and cube maps + // which will have D3DUSAGE_RENDERTARGET, but also need to have + // D3DUSAGE_DYNAMIC for locking/uploads to work. The flag combination + // isn't supported in D3D9, but we have a D3D7 exception in place. + // + if (m_commonSurf->IsRenderTarget() || initRT) { + Logger::debug("DDrawSurface::InitializeD3D9: Usage: D3DUSAGE_RENDERTARGET"); + pool = d3d9::D3DPOOL_DEFAULT; + usage |= D3DUSAGE_RENDERTARGET; + } + // All depth stencils will be created in DEFAULT + if (m_commonSurf->IsDepthStencil()) { + Logger::debug("DDrawSurface::InitializeD3D9: Usage: D3DUSAGE_DEPTHSTENCIL"); + pool = d3d9::D3DPOOL_DEFAULT; + usage |= D3DUSAGE_DEPTHSTENCIL; + } + + // General usage flags + if (m_commonSurf->IsTexture()) { + if (pool == d3d9::D3DPOOL_DEFAULT) { + Logger::debug("DDrawSurface::InitializeD3D9: Usage: D3DUSAGE_DYNAMIC"); + usage |= D3DUSAGE_DYNAMIC; + } + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) { + Logger::debug("DDrawSurface::InitializeD3D9: Usage: D3DUSAGE_AUTOGENMIPMAP"); + usage |= D3DUSAGE_AUTOGENMIPMAP; + } + } + + const char* poolPlacement = pool == d3d9::D3DPOOL_DEFAULT ? "D3DPOOL_DEFAULT" : + pool == d3d9::D3DPOOL_SYSTEMMEM ? "D3DPOOL_SYSTEMMEM" : "D3DPOOL_MANAGED"; + + Logger::debug(str::format("DDrawSurface::InitializeD3D9: Placing in: ", poolPlacement)); + + // Use the MSAA type that was determined to be supported during device creation + const d3d9::D3DMULTISAMPLE_TYPE multiSampleType = m_commonIntf->GetMultiSampleType(); + const uint32_t index = m_commonSurf->GetBackBufferIndex(); + + Com surf; + + HRESULT hr = DDERR_GENERIC; + + // Front Buffer + if (m_commonSurf->IsFrontBuffer()) { + Logger::debug("DDrawSurface::InitializeD3D9: Initializing front buffer..."); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to retrieve front buffer"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Back Buffer + } else if (m_commonSurf->IsBackBufferOrFlippable()) { + Logger::debug("DDrawSurface::InitializeD3D9: Initializing back buffer..."); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to retrieve back buffer"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Textures + } else if (m_commonSurf->IsTexture()) { + Logger::debug("DDrawSurface::InitializeD3D9: Initializing texture..."); + + Com tex; + + hr = m_d3d9Device->CreateTexture( + desc->dwWidth, desc->dwHeight, m_commonSurf->GetMipCount(), usage, + format, pool, &tex, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to create texture"); + m_texture9 = nullptr; + return hr; + } + + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) + tex->SetAutoGenFilterType(d3d9::D3DTEXF_ANISOTROPIC); + + // Attach level 0 to this surface + tex->GetSurfaceLevel(0, &surf); + m_d3d9 = (std::move(surf)); + + Logger::debug("DDrawSurface::InitializeD3D9: Created texture"); + m_texture9 = std::move(tex); + + // Depth Stencil + } else if (m_commonSurf->IsDepthStencil()) { + Logger::debug("DDrawSurface::InitializeD3D9: Initializing depth stencil..."); + + hr = m_d3d9Device->CreateDepthStencilSurface( + desc->dwWidth, desc->dwHeight, format, + multiSampleType, 0, FALSE, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to create DS"); + m_d3d9 = nullptr; + return hr; + } + + Logger::debug("DDrawSurface::InitializeD3D9: Created depth stencil surface"); + + m_d3d9 = std::move(surf); + + // Offscreen Plain Surfaces + } else if (m_commonSurf->IsOffScreenPlainSurface()) { + Logger::debug("DDrawSurface::InitializeD3D9: Initializing offscreen plain surface..."); + + // Sometimes we get passed offscreen plain surfaces which should be tied to the back buffer, + // either as existing RTs or during SetRenderTarget() calls, which are tracked with initRT + if (unlikely(m_commonIntf->IsCurrentRenderTarget(this) || initRT)) { + Logger::debug("DDrawSurface::InitializeD3D9: Offscreen plain surface is the RT"); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to retrieve offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + } else { + hr = m_d3d9Device->CreateOffscreenPlainSurface( + desc->dwWidth, desc->dwHeight, format, + pool, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to create offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + } + + m_d3d9 = std::move(surf); + + // Overlays (haven't seen any actual use of overlays in the wild) + } else if (m_commonSurf->IsOverlay()) { + Logger::debug("DDrawSurface::InitializeD3D9: Initializing overlay..."); + + // Always link overlays to the back buffer + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to retrieve overlay surface"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Generic render target + } else if (m_commonSurf->IsRenderTarget()) { + Logger::debug("DDrawSurface::InitializeD3D9: Initializing render target..."); + + // Must be lockable for blitting to work. Note that + // D3D9 does not allow the creation of lockable RTs when + // using MSAA, but we have a D3D7 exception in place. + hr = m_d3d9Device->CreateRenderTarget( + desc->dwWidth, desc->dwHeight, format, + multiSampleType, usage, TRUE, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to create render target"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // We sometimes get generic surfaces, with only dimensions, format and placement info + } else if (!m_commonSurf->IsNotKnown()) { + Logger::debug("DDrawSurface::InitializeD3D9: Initializing generic surface..."); + + hr = m_d3d9Device->CreateOffscreenPlainSurface( + desc->dwWidth, desc->dwHeight, format, + pool, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDrawSurface::InitializeD3D9: Failed to create offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + + Logger::debug("DDrawSurface::InitializeD3D9: Created offscreen plain surface"); + + m_d3d9 = std::move(surf); + } else { + Logger::warn("DDrawSurface::InitializeD3D9: Skipping initialization of unknown surface"); + } + + // Depth stencils will not need uploads post initialization + if (likely(!m_commonSurf->IsDepthStencil())) + UploadSurfaceData(); + + return DD_OK; + } + + inline HRESULT DDrawSurface::UploadSurfaceData() { + Logger::debug(str::format("DDrawSurface::UploadSurfaceData: Uploading nr. [[1-", m_surfCount, "]]")); + + if (unlikely(m_commonIntf->HasDrawn() && m_commonSurf->IsGuardableSurface())) { + Logger::debug("DDrawSurface::UploadSurfaceData: Skipping upload"); + return DD_OK; + } + + const d3d9::D3DFORMAT format = m_commonSurf->GetD3D9Format(); + + if (m_commonSurf->IsTexture()) { + BlitToD3D9Texture(m_texture9.ptr(), format, + m_proxy.ptr(), m_commonSurf->GetMipCount()); + // Blit surfaces directly + } else { + if (unlikely(m_commonSurf->IsDepthStencil())) { + if (likely(m_commonIntf->GetOptions()->uploadDepthStencils)) { + Logger::debug("DDrawSurface::UploadSurfaceData: Uploading depth stencil"); + } else { + Logger::debug("DDrawSurface::UploadSurfaceData: Skipping upload of depth stencil"); + return DD_OK; + } + } + + BlitToD3D9Surface(m_d3d9.ptr(), format, m_proxy.ptr()); + } + + return DD_OK; + } + + inline HRESULT DDrawSurface::CreateDeviceInternal(REFIID riid, void** ppvObject) { + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + DWORD deviceCreationFlags9 = D3DCREATE_SOFTWARE_VERTEXPROCESSING; + bool rgbFallback = false; + + if (likely(!d3dOptions->forceSWVP)) { + if (riid == IID_IDirect3DHALDevice) { + Logger::info("DDrawSurface::CreateDeviceInternal: Creating an IID_IDirect3DHALDevice device"); + deviceCreationFlags9 = D3DCREATE_MIXED_VERTEXPROCESSING; + } else if (riid == IID_IDirect3DRGBDevice) { + Logger::info("DDrawSurface::CreateDeviceInternal: Creating an IID_IDirect3DRGBDevice device"); + } else if (riid == IID_IDirect3DMMXDevice) { + Logger::warn("DDrawSurface::CreateDeviceInternal: Unsupported MMX device, falling back to RGB"); + rgbFallback = true; + } else if (riid == IID_IDirect3DRampDevice) { + Logger::warn("DDrawSurface::CreateDeviceInternal: Unsupported Ramp device, falling back to RGB"); + rgbFallback = true; + } else { + Logger::warn("DDrawSurface::CreateDeviceInternal: Unknown device identifier, falling back to RGB"); + Logger::warn(str::format(riid)); + rgbFallback = true; + } + } + + const IID rclsidOverride = rgbFallback ? IID_IDirect3DRGBDevice : riid; + + HWND hWnd = m_commonIntf->GetHWND(); + // Needed to sometimes safely skip intro playback on legacy devices + if (unlikely(hWnd == nullptr)) { + Logger::debug("DDrawSurface::CreateDeviceInternal: HWND is NULL"); + } + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(rclsidOverride, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + DWORD backBufferWidth = m_commonSurf->GetDesc()->dwWidth; + DWORD BackBufferHeight = m_commonSurf->GetDesc()->dwHeight; + + if (likely(!d3dOptions->forceProxiedPresent && + d3dOptions->backBufferResize)) { + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Ignore any mode size dimensions when in windowed present mode + if (exclusiveMode) { + DDrawModeSize* modeSize = m_commonIntf->GetModeSize(); + // Wayland apparently needs this for somewhat proper back buffer sizing + if ((modeSize->width && modeSize->width < m_commonSurf->GetDesc()->dwWidth) + || (modeSize->height && modeSize->height < m_commonSurf->GetDesc()->dwHeight)) { + Logger::info("DDrawSurface::CreateDeviceInternal: Enforcing mode dimensions"); + + backBufferWidth = modeSize->width; + BackBufferHeight = modeSize->height; + } + } + } + + D3D3Interface* d3d3Intf = m_commonIntf->GetD3D3Interface(); + if (unlikely(d3d3Intf == nullptr)) { + Logger::err("DDrawSurface::CreateDeviceInternal: Device creation failed due to null D3D3 interface"); + return DDERR_GENERIC; + } + + // Determine the supported AA sample count by querying the D3D9 interface + d3d9::D3DMULTISAMPLE_TYPE multiSampleType = d3d9::D3DMULTISAMPLE_NONE; + if (likely(d3dOptions->emulateFSAA != FSAAEmulation::Disabled)) { + HRESULT hr4S = d3d3Intf->GetD3D9()->CheckDeviceMultiSampleType(0, d3d9::D3DDEVTYPE_HAL, m_commonSurf->GetD3D9Format(), + TRUE, d3d9::D3DMULTISAMPLE_4_SAMPLES, NULL); + if (unlikely(FAILED(hr4S))) { + HRESULT hr2S = d3d3Intf->GetD3D9()->CheckDeviceMultiSampleType(0, d3d9::D3DDEVTYPE_HAL, m_commonSurf->GetD3D9Format(), + TRUE, d3d9::D3DMULTISAMPLE_2_SAMPLES, NULL); + if (unlikely(FAILED(hr2S))) { + Logger::warn("DDrawSurface::CreateDeviceInternal: No MSAA support has been detected"); + } else { + Logger::info("DDrawSurface::CreateDeviceInternal: Using 2x MSAA for FSAA emulation"); + multiSampleType = d3d9::D3DMULTISAMPLE_2_SAMPLES; + } + } else { + Logger::info("DDrawSurface::CreateDeviceInternal: Using 4x MSAA for FSAA emulation"); + multiSampleType = d3d9::D3DMULTISAMPLE_4_SAMPLES; + } + } else { + Logger::info("DDrawSurface::CreateDeviceInternal: FSAA emulation is disabled"); + } + + const DWORD cooperativeLevel = m_commonIntf->GetCooperativeLevel(); + + if ((cooperativeLevel & DDSCL_MULTITHREADED) || d3dOptions->forceMultiThreaded) { + Logger::info("DDrawSurface::CreateDeviceInternal: Using thread safe runtime synchronization"); + deviceCreationFlags9 |= D3DCREATE_MULTITHREADED; + } + // DDSCL_FPUPRESERVE does not exist prior to DDraw7, + // and DDSCL_FPUSETUP is NOT the default state + if (!(cooperativeLevel & DDSCL_FPUSETUP)) + deviceCreationFlags9 |= D3DCREATE_FPU_PRESERVE; + if (cooperativeLevel & DDSCL_NOWINDOWCHANGES) + deviceCreationFlags9 |= D3DCREATE_NOWINDOWCHANGES; + + Logger::info(str::format("DDrawSurface::CreateDeviceInternal: Back buffer size: ", backBufferWidth, "x", BackBufferHeight)); + + DWORD backBufferCount = 0; + if (likely(!d3dOptions->forceSingleBackBuffer)) { + IDirectDrawSurface* backBuffer = m_proxy.ptr(); + while (backBuffer != nullptr) { + IDirectDrawSurface* parentSurface = backBuffer; + backBuffer = nullptr; + parentSurface->EnumAttachedSurfaces(&backBuffer, ListBackBufferSurfacesCallback); + backBufferCount++; + // the swapchain will eventually return to its origin + if (backBuffer == m_proxy.ptr()) + break; + } + } + // Consider the front buffer as well when reporting the overall count + Logger::info(str::format("DDrawSurface::CreateDeviceInternal: Back buffer count: ", backBufferCount + 1)); + + d3d9::D3DPRESENT_PARAMETERS params; + params.BackBufferWidth = backBufferWidth; + params.BackBufferHeight = BackBufferHeight; + params.BackBufferFormat = m_commonSurf->GetD3D9Format(); + params.BackBufferCount = backBufferCount; + params.MultiSampleType = multiSampleType; + params.MultiSampleQuality = 0; + params.SwapEffect = d3d9::D3DSWAPEFFECT_DISCARD; + params.hDeviceWindow = hWnd; + params.Windowed = TRUE; // Always use windowed, so that we can delegate mode switching to ddraw + params.EnableAutoDepthStencil = FALSE; + params.AutoDepthStencilFormat = d3d9::D3DFMT_UNKNOWN; + params.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; // Needed for back buffer locks + params.FullScreen_RefreshRateInHz = 0; // We'll get the right mode/refresh rate set by ddraw, just play along + params.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; // A D3D3 device always uses VSync + + Com device9; + hr = d3d3Intf->GetD3D9()->CreateDevice( + D3DADAPTER_DEFAULT, + d3d9::D3DDEVTYPE_HAL, + hWnd, + deviceCreationFlags9, + ¶ms, + &device9 + ); + + if (unlikely(FAILED(hr))) { + Logger::err("DDrawSurface::CreateDeviceInternal: Failed to create the D3D9 device"); + return hr; + } + + Com device3 = new D3D3Device(std::move(ppvProxyObject), this, GetD3D3Caps(d3dOptions), + rclsidOverride, params, std::move(device9), deviceCreationFlags9); + + // Set the newly created D3D3 device on the common interface + m_commonIntf->SetD3D3Device(device3.ptr()); + // Now that we have a valid D3D9 device pointer, we can initialize the depth stencil (if any) + device3->InitializeDS(); + + *ppvObject = device3.ref(); + + return DD_OK; + } + +} diff --git a/src/ddraw/ddraw/ddraw_surface.h b/src/ddraw/ddraw/ddraw_surface.h new file mode 100644 index 00000000000..9b7ba74fac4 --- /dev/null +++ b/src/ddraw/ddraw/ddraw_surface.h @@ -0,0 +1,224 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_util.h" + +#include "../ddraw_common_surface.h" + +#include "ddraw_interface.h" + +#include "../d3d3/d3d3_texture.h" +#include "../d3d5/d3d5_texture.h" + +#include + +namespace dxvk { + + class DDrawInterface; + class DDraw7Surface; + + /** + * \brief IDirectDrawSurface interface implementation + */ + class DDrawSurface final : public DDrawWrappedObject { + + public: + + DDrawSurface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDrawInterface* pParent, + DDrawSurface* pParentSurf, + bool isChildObject); + + ~DDrawSurface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE AddAttachedSurface(LPDIRECTDRAWSURFACE lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE AddOverlayDirtyRect(LPRECT lpRect); + + HRESULT STDMETHODCALLTYPE Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx); + + HRESULT STDMETHODCALLTYPE BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans); + + HRESULT STDMETHODCALLTYPE DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback); + + HRESULT STDMETHODCALLTYPE Flip(LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetAttachedSurface(LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE *lplpDDAttachedSurface); + + HRESULT STDMETHODCALLTYPE GetBltStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDSCAPS lpDDSCaps); + + HRESULT STDMETHODCALLTYPE GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper); + + HRESULT STDMETHODCALLTYPE GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE GetDC(HDC *lphDC); + + HRESULT STDMETHODCALLTYPE GetFlipStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetOverlayPosition(LPLONG lplX, LPLONG lplY); + + HRESULT STDMETHODCALLTYPE GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette); + + HRESULT STDMETHODCALLTYPE GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat); + + HRESULT STDMETHODCALLTYPE GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE IsLost(); + + HRESULT STDMETHODCALLTYPE Lock(LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent); + + HRESULT STDMETHODCALLTYPE ReleaseDC(HDC hDC); + + HRESULT STDMETHODCALLTYPE Restore(); + + HRESULT STDMETHODCALLTYPE SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper); + + HRESULT STDMETHODCALLTYPE SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE SetOverlayPosition(LONG lX, LONG lY); + + HRESULT STDMETHODCALLTYPE SetPalette(LPDIRECTDRAWPALETTE lpDDPalette); + + HRESULT STDMETHODCALLTYPE Unlock(LPVOID lpSurfaceData); + + HRESULT STDMETHODCALLTYPE UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx); + + HRESULT STDMETHODCALLTYPE UpdateOverlayDisplay(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE lpDDSReference); + + DDrawCommonSurface* GetCommonSurface() const { + return m_commonSurf.ptr(); + } + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + d3d9::IDirect3DDevice9* GetD3D9Device() const { + return m_d3d9Device; + } + + d3d9::IDirect3DTexture9* GetD3D9Texture() const { + return m_texture9.ptr(); + } + + D3D3Texture* GetD3D3Texture() const { + return m_texture3.ptr(); + } + + void SetD3D3Texture(D3D3Texture* texture3) { + m_texture3 = texture3; + } + + D3D5Texture* GetD3D5Texture() const { + return m_texture5.ptr(); + } + + void SetD3D5Texture(D3D5Texture* texture5) { + m_texture5 = texture5; + } + + DDrawSurface* GetAttachedDepthStencil() { + // Fast path, since in most cases we already store the required surface + if (likely(m_depthStencil.ptr() != nullptr)) + return m_depthStencil.ptr(); + + DDSCAPS caps; + caps.dwCaps = DDSCAPS_ZBUFFER; + IDirectDrawSurface* surface = nullptr; + HRESULT hr = GetAttachedSurface(&caps, &surface); + if (unlikely(FAILED(hr))) + return nullptr; + + m_depthStencil = reinterpret_cast(surface); + + return m_depthStencil.ptr(); + } + + void ClearAttachedDepthStencil() { + m_depthStencil = nullptr; + } + + void SetParentSurface(DDrawSurface* surface) { + m_parentSurf = surface; + m_commonSurf->SetIsAttached(true); + } + + void ClearParentSurface() { + m_parentSurf = nullptr; + m_commonSurf->SetIsAttached(false); + } + + HRESULT InitializeD3D9RenderTarget(); + + HRESULT InitializeD3D9DepthStencil(); + + HRESULT InitializeOrUploadD3D9(); + + HRESULT InitializeD3D9(const bool initRT); + + private: + + inline HRESULT UploadSurfaceData(); + + inline HRESULT CreateDeviceInternal(REFIID riid, void** ppvObject); + + inline void RefreshD3D9Device() { + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (unlikely(m_d3d9Device != d3d9Device)) { + // Check if the device has been recreated and reset all D3D9 resources + if (m_d3d9Device != nullptr) { + Logger::debug("DDrawSurface: Device context has changed, clearing all D3D9 resources"); + m_texture9 = nullptr; + m_d3d9 = nullptr; + } + m_d3d9Device = d3d9Device; + } + } + + bool m_isChildObject = true; + + static uint32_t s_surfCount; + uint32_t m_surfCount = 0; + + Com m_commonSurf; + DDrawCommonInterface* m_commonIntf = nullptr; + + DDrawSurface* m_parentSurf = nullptr; + + d3d9::IDirect3DDevice9* m_d3d9Device = nullptr; + + Com m_texture3; + Com m_texture5; + + Com m_texture9; + + // Back buffers will have depth stencil surfaces as attachments (in practice + // I have never seen more than one depth stencil being attached at a time) + Com m_depthStencil; + + // These are attached surfaces, which are typically mips or other types of generated + // surfaces, which need to exist for the entire lifecycle of their parent surface. + // They are implemented with linked list, so for example only one mip level + // will be held in a parent texture, and the next mip level will be held in the previous mip. + std::unordered_map> m_attachedSurfaces; + + }; + +} diff --git a/src/ddraw/ddraw2/ddraw2_interface.cpp b/src/ddraw/ddraw2/ddraw2_interface.cpp new file mode 100644 index 00000000000..3681fbf93a0 --- /dev/null +++ b/src/ddraw/ddraw2/ddraw2_interface.cpp @@ -0,0 +1,668 @@ +#include "ddraw2_interface.h" + +#include "../ddraw_clipper.h" +#include "../ddraw_palette.h" + +#include "../ddraw/ddraw_surface.h" +#include "../ddraw/ddraw_interface.h" +#include "../ddraw4/ddraw4_interface.h" +#include "../ddraw7/ddraw7_interface.h" + +#include "../d3d3/d3d3_interface.h" +#include "../d3d5/d3d5_interface.h" +#include "../d3d6/d3d6_interface.h" +#include + +namespace dxvk { + + uint32_t DDraw2Interface::s_intfCount = 0; + + DDraw2Interface::DDraw2Interface( + DDrawCommonInterface* commonIntf, + Com&& proxyIntf) + : DDrawWrappedObject(nullptr, std::move(proxyIntf), nullptr) + , m_commonIntf ( commonIntf ) { + + if (m_commonIntf->GetOrigin() == nullptr) + m_commonIntf->SetOrigin(this); + + m_commonIntf->SetDD2Interface(this); + + static bool s_apitraceModeWarningShown; + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && + !std::exchange(s_apitraceModeWarningShown, true))) + Logger::warn("DDraw2Interface: Apitrace mode is enabled. Performance will be suboptimal!"); + + m_intfCount = ++s_intfCount; + + Logger::debug(str::format("DDraw2Interface: Created a new interface nr. <<2-", m_intfCount, ">>")); + } + + DDraw2Interface::~DDraw2Interface() { + if (m_commonIntf->GetOrigin() == this) + m_commonIntf->SetOrigin(nullptr); + + m_commonIntf->SetDD2Interface(nullptr); + + Logger::debug(str::format("DDraw2Interface: Interface nr. <<2-", m_intfCount, ">> bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDraw2Interface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + // Standard way of retrieving a D3D5 interface + if (riid == __uuidof(IDirect3D2)) { + if (m_commonIntf->GetDDInterface() != nullptr) { + Logger::debug("DDraw2Interface::QueryInterface: Query for IDirect3D2"); + return m_commonIntf->GetDDInterface()->QueryInterface(riid, ppvObject); + } + + Logger::warn("DDraw2Interface::QueryInterface: Query for IDirect3D2"); + return m_proxy->QueryInterface(riid, ppvObject); + } + // Some games query for legacy DDraw interfaces + if (unlikely(riid == __uuidof(IDirectDraw))) { + if (m_commonIntf->GetDDInterface() != nullptr) { + Logger::debug("DDraw2Interface::QueryInterface: Query for existing IDirectDraw"); + return m_commonIntf->GetDDInterface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Interface::QueryInterface: Query for IDirectDraw"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDrawInterface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Legacy way of getting a DDraw4 interface + if (riid == __uuidof(IDirectDraw4)) { + if (m_commonIntf->GetDD4Interface() != nullptr) { + Logger::debug("DDraw2Interface::QueryInterface: Query for existing IDirectDraw4"); + return m_commonIntf->GetDD4Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Interface::QueryInterface: Query for IDirectDraw4"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw4Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Legacy way of getting a DDraw7 interface + if (unlikely(riid == __uuidof(IDirectDraw7))) { + if (m_commonIntf->GetDD7Interface() != nullptr) { + Logger::debug("DDraw2Interface::QueryInterface: Query for existing IDirectDraw7"); + return m_commonIntf->GetDD7Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Interface::QueryInterface: Query for IDirectDraw7"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw7Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Standard way of retrieving a D3D3 interface + if (unlikely(riid == __uuidof(IDirect3D))) { + if (m_commonIntf->GetDDInterface() != nullptr) { + Logger::debug("DDraw2Interface::QueryInterface: Query for IDirect3D"); + return m_commonIntf->GetDDInterface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Interface::QueryInterface: Query for IDirect3D"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + Com d3d3Intf = new D3D3Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + m_commonIntf->SetD3D3Interface(d3d3Intf.ptr()); + *ppvObject = d3d3Intf.ref(); + + return S_OK; + } + // Standard way of retrieving a D3D5 interface + if (unlikely(riid == __uuidof(IDirect3D2))) { + if (m_commonIntf->GetDDInterface() != nullptr) { + Logger::debug("DDraw2Interface::QueryInterface: Forwarded query for IDirect3D2"); + return m_commonIntf->GetDDInterface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Interface::QueryInterface: Query for IDirect3D2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + Com d3d5Intf = new D3D5Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + *ppvObject = d3d5Intf.ref(); + + return S_OK; + } + // Standard way of retrieving a D3D6 interface + if (unlikely(riid == __uuidof(IDirect3D3))) { + Logger::debug("DDraw2Interface::QueryInterface: Query for IDirect3D3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + Com d3d6Intf = new D3D6Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + *ppvObject = d3d6Intf.ref(); + + return S_OK; + } + // Quite a lot of games query for this IID during intro playback + if (unlikely(riid == GUID_IAMMediaStream)) { + Logger::debug("DDraw2Interface::QueryInterface: Query for IAMMediaStream"); + return m_proxy->QueryInterface(riid, ppvObject); + } + // Also seen queried by some games, such as V-Rally 2: Expert Edition + if (unlikely(riid == GUID_IMediaStream)) { + Logger::debug("DDraw2Interface::QueryInterface: Query for IMediaStream"); + return m_proxy->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // The documentation states: "The IDirectDraw2::Compact method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw2Interface::Compact() { + Logger::debug(">>> DDraw2Interface::Compact"); + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::CreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw2Interface::CreateClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + Com lplpDDClipperProxy; + HRESULT hr = m_proxy->CreateClipper(dwFlags, &lplpDDClipperProxy, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + *lplpDDClipper = ref(new DDrawClipper(std::move(lplpDDClipperProxy), this)); + } else { + Logger::warn("DDraw2Interface::CreateClipper: Failed to create proxy clipper"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::CreatePalette(DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw2Interface::CreatePalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + Com lplpDDPaletteProxy; + HRESULT hr = m_proxy->CreatePalette(dwFlags, lpColorTable, &lplpDDPaletteProxy, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + // Palettes created from IDirectDraw and IDirectDraw2 do not ref their parent interfaces + *lplpDDPalette = ref(new DDrawPalette(std::move(lplpDDPaletteProxy), nullptr)); + } else { + Logger::warn("DDraw2Interface::CreatePalette: Failed to create proxy palette"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::CreateSurface(LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw2Interface::CreateSurface"); + + // The cooperative level is always checked first + if (unlikely(!m_commonIntf->IsCooperativeLevelSet())) + return DDERR_NOCOOPERATIVELEVELSET; + + if (unlikely(lpDDSurfaceDesc == nullptr || lplpDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDSurface); + + // Because we are removing the DDSCAPS_WRITEONLY flag below, we need + // to first validate the combinations that would otherwise cause issues + HRESULT hr = ValidateSurfaceFlags(lpDDSurfaceDesc); + if (unlikely(FAILED(hr))) + return hr; + + // We need to ensure we can always read from surfaces for upload to + // D3D9, so always strip the DDSCAPS_WRITEONLY flag on creation + lpDDSurfaceDesc->ddsCaps.dwCaps &= ~DDSCAPS_WRITEONLY; + + if (unlikely((lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_ZBUFFER) + && (lpDDSurfaceDesc->ddpfPixelFormat.dwZBitMask == 0xFFFFFFFF))) { + if (m_commonIntf->GetOptions()->useD24X8forD32) { + // In case of up-front unsupported and unadvertised D32 depth stencil use, + // replace it with D24X8, as some games, such as Sacrifice, rely on it + // to properly enable 32-bit display modes (and revert to 16-bit otherwise) + Logger::info("DDraw2Interface::CreateSurface: Using D24X8 instead of D32"); + lpDDSurfaceDesc->ddpfPixelFormat.dwZBitMask = 0xFFFFFF; + } else { + Logger::warn("DDraw2Interface::CreateSurface: Use of unsupported D32"); + } + } + + Com ddrawSurfaceProxied; + hr = m_proxy->CreateSurface(lpDDSurfaceDesc, &ddrawSurfaceProxied, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + try{ + // Surfaces created from IDirectDraw and IDirectDraw2 do not ref their parent interfaces + Com surface = new DDrawSurface(nullptr, std::move(ddrawSurfaceProxied), + m_commonIntf->GetDDInterface(), nullptr, false); + if (unlikely(lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)) + m_commonIntf->SetPrimarySurface(surface->GetCommonSurface()); + *lplpDDSurface = surface.ref(); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } else { + Logger::debug("DDraw2Interface::CreateSurface: Failed to create proxy surface"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::DuplicateSurface(LPDIRECTDRAWSURFACE lpDDSurface, LPDIRECTDRAWSURFACE *lplpDupDDSurface) { + Logger::debug("<<< DDraw2Interface::DuplicateSurface: Proxy"); + + if (m_commonIntf->IsWrappedSurface(lpDDSurface)) { + InitReturnPtr(lplpDupDDSurface); + + DDrawSurface* ddrawSurface = static_cast(lpDDSurface); + Com dupSurface; + HRESULT hr = m_proxy->DuplicateSurface(ddrawSurface->GetProxied(), &dupSurface); + if (likely(SUCCEEDED(hr))) { + try { + *lplpDupDDSurface = ref(new DDrawSurface(nullptr, std::move(dupSurface), + m_commonIntf->GetDDInterface(), nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } + return hr; + } else { + if (unlikely(lpDDSurface != nullptr)) { + Logger::warn("DDraw2Interface::DuplicateSurface: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + return m_proxy->DuplicateSurface(lpDDSurface, lplpDupDDSurface); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::EnumDisplayModes(DWORD dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback) { + Logger::debug("<<< DDraw2Interface::EnumDisplayModes: Proxy"); + return m_proxy->EnumDisplayModes(dwFlags, lpDDSurfaceDesc, lpContext, lpEnumModesCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::EnumSurfaces(DWORD dwFlags, LPDDSURFACEDESC lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) { + Logger::warn("<<< DDraw2Interface::EnumSurfaces: Proxy"); + return m_proxy->EnumSurfaces(dwFlags, lpDDSD, lpContext, lpEnumSurfacesCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::FlipToGDISurface() { + Logger::debug("*** DDraw2Interface::FlipToGDISurface: Ignoring"); + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) { + Logger::debug("<<< DDraw2Interface::GetCaps: Proxy"); + + HRESULT hr = m_proxy->GetCaps(lpDDDriverCaps, lpDDHELCaps); + if (unlikely(FAILED(hr))) + return hr; + + static constexpr DWORD Megabytes = 1024 * 1024; + static constexpr DWORD MaxMemory = ddrawCaps::MaxTextureMemory * Megabytes; + static constexpr DWORD ReservedMemory = ddrawCaps::ReservedTextureMemory * Megabytes; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + // Properly fill in the dwVidMemTotal / dwVidMemFree fields + DWORD total9 = 0; + DWORD free9 = 0; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (likely(d3d9Device != nullptr)) { + Logger::debug("DDraw2Interface::GetCaps: Getting memory stats from D3D9"); + + total9 = static_cast(m_commonIntf->GetTotalTextureMemory()); + free9 = static_cast(d3d9Device->GetAvailableTextureMem()); + + if (likely(total9 >= MaxMemory)) { + const DWORD delta = total9 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free9 > delta + ReservedMemory ? free9 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw2Interface::GetCaps: Total: ", total9)); + Logger::debug(str::format("DDraw2Interface::GetCaps: Free : ", free9)); + } else { + Logger::debug("DDraw2Interface::GetCaps: Getting memory stats from DDraw"); + + const DWORD total5 = lpDDDriverCaps != nullptr ? lpDDDriverCaps->dwVidMemTotal : 0; + const DWORD free5 = lpDDDriverCaps != nullptr ? lpDDDriverCaps->dwVidMemFree : 0; + + Logger::debug(str::format("DDraw2Interface::GetCaps: DDraw Total: ", total5)); + Logger::debug(str::format("DDraw2Interface::GetCaps: DDraw Free : ", free5)); + + if (unlikely(total5 < MaxMemory)) { + total9 = total5; + free9 = free5; + } else { + const DWORD delta = total5 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free5 > delta + ReservedMemory ? free5 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw2Interface::GetCaps: Total: ", total9)); + Logger::debug(str::format("DDraw2Interface::GetCaps: Free : ", free9)); + } + + if (lpDDDriverCaps != nullptr) { + lpDDDriverCaps->dwZBufferBitDepths = d3dOptions->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + lpDDDriverCaps->dwVidMemTotal = total9; + lpDDDriverCaps->dwVidMemFree = free9; + lpDDDriverCaps->dwNumFourCCCodes = ddrawCaps::NumberOfFOURCCCodes; + } + if (lpDDHELCaps != nullptr) { + lpDDHELCaps->dwZBufferBitDepths = d3dOptions->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + lpDDHELCaps->dwVidMemTotal = total9; + lpDDHELCaps->dwVidMemFree = free9; + lpDDHELCaps->dwNumFourCCCodes = ddrawCaps::NumberOfFOURCCCodes; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::GetDisplayMode(LPDDSURFACEDESC lpDDSurfaceDesc) { + Logger::debug("<<< DDraw2Interface::GetDisplayMode: Proxy"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + HRESULT hr = m_proxy->GetDisplayMode(lpDDSurfaceDesc); + if (unlikely(FAILED(hr))) + return hr; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->mask8BitModes && lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount == 8)) + lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount = 16; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::GetFourCCCodes(LPDWORD lpNumCodes, LPDWORD lpCodes) { + Logger::debug(">>> DDraw2Interface::GetFourCCCodes"); + + if (likely(lpNumCodes != nullptr && lpCodes != nullptr)) { + const uint32_t copyNumCodes = std::min(ddrawCaps::NumberOfFOURCCCodes, *lpNumCodes); + for (uint32_t i = 0; i < copyNumCodes; i++) { + lpCodes[i] = ddrawCaps::SupportedFourCCs[i]; + } + } + + if (lpNumCodes != nullptr) + *lpNumCodes = ddrawCaps::NumberOfFOURCCCodes; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::GetGDISurface(LPDIRECTDRAWSURFACE *lplpGDIDDSurface) { + Logger::debug("<<< DDraw2Interface::GetGDISurface: Proxy"); + + if(unlikely(lplpGDIDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpGDIDDSurface); + + Com gdiSurface; + HRESULT hr = m_proxy->GetGDISurface(&gdiSurface); + + if (unlikely(FAILED(hr))) { + Logger::debug("DDraw2Interface::GetGDISurface: Failed to retrieve GDI surface"); + return hr; + } + + if (unlikely(m_commonIntf->IsWrappedSurface(gdiSurface.ptr()))) { + *lplpGDIDDSurface = gdiSurface.ref(); + } else { + Logger::debug("DDraw2Interface::GetGDISurface: Received a non-wrapped GDI surface"); + try { + *lplpGDIDDSurface = ref(new DDrawSurface(nullptr, std::move(gdiSurface), m_commonIntf->GetDDInterface(), nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::GetMonitorFrequency(LPDWORD lpdwFrequency) { + Logger::debug("<<< DDraw2Interface::GetMonitorFrequency: Proxy"); + return m_proxy->GetMonitorFrequency(lpdwFrequency); + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::GetScanLine(LPDWORD lpdwScanLine) { + Logger::debug("<<< DDraw2Interface::GetScanLine: Proxy"); + return m_proxy->GetScanLine(lpdwScanLine); + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::GetVerticalBlankStatus(LPBOOL lpbIsInVB) { + Logger::debug("<<< DDraw2Interface::GetVerticalBlankStatus: Proxy"); + return m_proxy->GetVerticalBlankStatus(lpbIsInVB); + } + + // Should technically always return DDERR_ALREADYINITIALIZED, unless the + // interface is created via IClassFactory, however Requiem: Avenging Angel + // expects it to work on a regular interface too, after initially creating + // and releasing an interface through IClassFactory (but never initializing it). + // On native DDraw the initial interface most likely gets reused. In practice, + // applications that don't use IClassFactory won't call this, so keep it simple. + HRESULT STDMETHODCALLTYPE DDraw2Interface::Initialize(GUID* lpGUID) { + Logger::debug(">>> DDraw2Interface::Initialize"); + + if (unlikely(m_commonIntf->IsInitialized())) + return DDERR_ALREADYINITIALIZED; + + m_commonIntf->MarkAsInitialized(); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::RestoreDisplayMode() { + Logger::debug("<<< DDraw2Interface::RestoreDisplayMode: Proxy"); + return m_proxy->RestoreDisplayMode(); + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::SetCooperativeLevel(HWND hWnd, DWORD dwFlags) { + Logger::debug("<<< DDraw2Interface::SetCooperativeLevel: Proxy"); + + HRESULT hr = m_proxy->SetCooperativeLevel(hWnd, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + m_commonIntf->SetCooperativeLevel(hWnd, dwFlags); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags) { + Logger::debug("<<< DDraw2Interface::SetDisplayMode: Proxy"); + + Logger::debug(str::format("DDraw2Interface::SetDisplayMode: ", dwWidth, "x", dwHeight, ":", dwBPP, "@", dwRefreshRate)); + + HRESULT hr = m_proxy->SetDisplayMode(dwWidth, dwHeight, dwBPP, dwRefreshRate, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + DDrawCommonSurface* ps = m_commonIntf->GetPrimarySurface(); + + if (likely(ps != nullptr)) { + hr = ps->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::warn("DDraw2Interface::SetDisplayMode: Failed to update primary surface desc"); + } + + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent && + m_commonIntf->GetOptions()->backBufferResize)) { + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Ignore any mode size dimensions when in windowed present mode + if (exclusiveMode) { + Logger::debug("DDraw2Interface::SetDisplayMode: Exclusive full-screen present mode in use"); + DDrawModeSize* modeSize = m_commonIntf->GetModeSize(); + if (modeSize->width != dwWidth || modeSize->height != dwHeight) { + modeSize->width = dwWidth; + modeSize->height = dwHeight; + } + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::WaitForVerticalBlank(DWORD dwFlags, HANDLE hEvent) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw2Interface::WaitForVerticalBlank: Proxy"); + m_proxy->WaitForVerticalBlank(dwFlags, hEvent); + } + + Logger::debug(">>> DDraw2Interface::WaitForVerticalBlank"); + + if (unlikely(dwFlags & DDWAITVB_BLOCKBEGINEVENT)) + return DDERR_UNSUPPORTED; + + // Switch to a default presentation interval when an application + // tries to wait for vertical blank, if we're not already doing so + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (unlikely(d3d9Device != nullptr && !m_commonIntf->GetWaitForVBlank())) { + Logger::info("DDraw2Interface::WaitForVerticalBlank: Switching to D3DPRESENT_INTERVAL_DEFAULT for presentation"); + + d3d9::D3DPRESENT_PARAMETERS resetParams = m_commonIntf->GetPresentParameters(); + resetParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + HRESULT hrReset = m_commonIntf->ResetD3D9Swapchain(&resetParams); + if (likely(SUCCEEDED(hrReset))) + m_commonIntf->SetWaitForVBlank(true); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Interface::GetAvailableVidMem(LPDDSCAPS lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree) { + Logger::debug(">>> DDraw2Interface::GetAvailableVidMem"); + + if (unlikely(lpdwTotal == nullptr && lpdwFree == nullptr)) + return DD_OK; + + static constexpr DWORD Megabytes = 1024 * 1024; + static constexpr DWORD MaxMemory = ddrawCaps::MaxTextureMemory * Megabytes; + static constexpr DWORD ReservedMemory = ddrawCaps::ReservedTextureMemory * Megabytes; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (likely(d3d9Device != nullptr)) { + Logger::debug("DDraw2Interface::GetAvailableVidMem: Getting memory stats from D3D9"); + + DWORD total9 = static_cast(m_commonIntf->GetTotalTextureMemory()); + DWORD free9 = static_cast(d3d9Device->GetAvailableTextureMem()); + + if (likely(total9 >= MaxMemory)) { + const DWORD delta = total9 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free9 > delta + ReservedMemory ? free9 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw2Interface::GetAvailableVidMem: Total: ", total9)); + Logger::debug(str::format("DDraw2Interface::GetAvailableVidMem: Free : ", free9)); + + if (lpdwTotal != nullptr) + *lpdwTotal = total9; + if (lpdwFree != nullptr) + *lpdwFree = free9; + + } else { + Logger::debug("DDraw2Interface::GetAvailableVidMem: Getting memory stats from DDraw"); + + DWORD total5 = 0; + DWORD free5 = 0; + + HRESULT hr = m_proxy->GetAvailableVidMem(lpDDCaps, &total5, &free5); + if (unlikely(FAILED(hr))) { + Logger::err("DDraw2Interface::GetAvailableVidMem: Failed proxied call"); + if (lpdwTotal != nullptr) + *lpdwTotal = 0; + if (lpdwFree != nullptr) + *lpdwFree = 0; + return hr; + } + + Logger::debug(str::format("DDraw2Interface::GetAvailableVidMem: DDraw Total: ", total5)); + Logger::debug(str::format("DDraw2Interface::GetAvailableVidMem: DDraw Free : ", free5)); + + DWORD total9 = 0; + DWORD free9 = 0; + + if (unlikely(total5 < MaxMemory)) { + total9 = total5; + free9 = free5; + } else { + const DWORD delta = total5 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free5 > delta + ReservedMemory ? free5 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw2Interface::GetAvailableVidMem: Total: ", total9)); + Logger::debug(str::format("DDraw2Interface::GetAvailableVidMem: Free : ", free9)); + + if (lpdwTotal != nullptr) + *lpdwTotal = total9; + if (lpdwFree != nullptr) + *lpdwFree = free9; + } + + return DD_OK; + } + +} diff --git a/src/ddraw/ddraw2/ddraw2_interface.h b/src/ddraw/ddraw2/ddraw2_interface.h new file mode 100644 index 00000000000..c3264b3c657 --- /dev/null +++ b/src/ddraw/ddraw2/ddraw2_interface.h @@ -0,0 +1,85 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_caps.h" +#include "../ddraw_format.h" + +#include "../ddraw_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +namespace dxvk { + + class DDrawSurface; + + /** + * \brief DirectDraw2 interface implementation + */ + class DDraw2Interface final : public DDrawWrappedObject { + + public: + DDraw2Interface( + DDrawCommonInterface* commonIntf, + Com&& proxyIntf); + + ~DDraw2Interface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE Compact(); + + HRESULT STDMETHODCALLTYPE CreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreatePalette(DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateSurface(LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE DuplicateSurface(LPDIRECTDRAWSURFACE lpDDSurface, LPDIRECTDRAWSURFACE *lplpDupDDSurface); + + HRESULT STDMETHODCALLTYPE EnumDisplayModes(DWORD dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback); + + HRESULT STDMETHODCALLTYPE EnumSurfaces(DWORD dwFlags, LPDDSURFACEDESC lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE FlipToGDISurface(); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps); + + HRESULT STDMETHODCALLTYPE GetDisplayMode(LPDDSURFACEDESC lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE GetFourCCCodes(LPDWORD lpNumCodes, LPDWORD lpCodes); + + HRESULT STDMETHODCALLTYPE GetGDISurface(LPDIRECTDRAWSURFACE *lplpGDIDDSurface); + + HRESULT STDMETHODCALLTYPE GetMonitorFrequency(LPDWORD lpdwFrequency); + + HRESULT STDMETHODCALLTYPE GetScanLine(LPDWORD lpdwScanLine); + + HRESULT STDMETHODCALLTYPE GetVerticalBlankStatus(LPBOOL lpbIsInVB); + + HRESULT STDMETHODCALLTYPE Initialize(GUID* lpGUID); + + HRESULT STDMETHODCALLTYPE RestoreDisplayMode(); + + HRESULT STDMETHODCALLTYPE SetCooperativeLevel(HWND hWnd, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE WaitForVerticalBlank(DWORD dwFlags, HANDLE hEvent); + + HRESULT STDMETHODCALLTYPE GetAvailableVidMem(LPDDSCAPS lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf.ptr(); + } + + private: + + static uint32_t s_intfCount; + uint32_t m_intfCount = 0; + + Com m_commonIntf; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw2/ddraw2_surface.cpp b/src/ddraw/ddraw2/ddraw2_surface.cpp new file mode 100644 index 00000000000..4c60b5f42a3 --- /dev/null +++ b/src/ddraw/ddraw2/ddraw2_surface.cpp @@ -0,0 +1,1003 @@ +#include "ddraw2_surface.h" + +#include "../ddraw_gamma.h" + +#include "../ddraw/ddraw_interface.h" +#include "../ddraw/ddraw_surface.h" +#include "../ddraw2/ddraw3_surface.h" +#include "../ddraw4/ddraw4_surface.h" +#include "../ddraw7/ddraw7_surface.h" + +#include "../d3d3/d3d3_texture.h" +#include "../d3d5/d3d5_device.h" +#include +#include "../d3d5/d3d5_texture.h" + +namespace dxvk { + + uint32_t DDraw2Surface::s_surfCount = 0; + + DDraw2Surface::DDraw2Surface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDrawSurface* pParent, + DDraw2Surface* pParentSurf) + : DDrawWrappedObject(pParent, std::move(surfProxy), nullptr) + , m_commonSurf ( commonSurf ) + , m_parentSurf ( pParentSurf ) { + if (m_parent != nullptr) { + m_commonIntf = m_parent->GetCommonInterface(); + } else if (m_parentSurf != nullptr) { + m_commonIntf = m_parentSurf->GetCommonInterface(); + } else if (m_commonSurf != nullptr) { + m_commonIntf = m_commonSurf->GetCommonInterface(); + } else { + throw DxvkError("DDraw2Surface: ERROR! Failed to retrieve the common interface!"); + } + + if (m_commonSurf == nullptr) + m_commonSurf = new DDrawCommonSurface(m_commonIntf); + + // Retrieve and cache the proxy surface desc + if (unlikely(!m_commonSurf->IsDescSet())) { + DDSURFACEDESC desc; + desc.dwSize = sizeof(DDSURFACEDESC); + HRESULT hr = m_proxy->GetSurfaceDesc(&desc); + + if (unlikely(FAILED(hr))) { + throw DxvkError("DDraw2Surface: ERROR! Failed to retrieve new surface desc!"); + } else { + m_commonSurf->SetDesc(desc); + } + } + + // We need to keep the IDirectDrawSurface parent around, since we're entirely dependent on it. + // If one doesn't exist (e.g. attached surfaces), then use QueryInterface and cache it. + if (m_parent == nullptr) { + Com originSurf; + HRESULT hr = m_proxy->QueryInterface(__uuidof(IDirectDrawSurface), reinterpret_cast(&originSurf)); + if (unlikely(FAILED(hr))) + throw DxvkError("DDraw2Surface: ERROR! Failed to retrieve an IDirectDrawSurface interface!"); + + m_originSurf = new DDrawSurface(m_commonSurf.ptr(), std::move(originSurf), m_commonIntf->GetDDInterface(), nullptr, false); + m_parent = m_originSurf.ptr(); + } else { + m_originSurf = m_parent; + } + + m_commonIntf->AddWrappedSurface(this); + + m_commonSurf->SetDD2Surface(this); + + m_surfCount = ++s_surfCount; + + Logger::debug(str::format("DDraw2Surface: Created a new surface nr. [[2-", m_surfCount, "]]")); + + // Note: IDirectDrawSurface2 can't ever be the origin surface + } + + DDraw2Surface::~DDraw2Surface() { + // Clear the cached depth stencil on the parent if matched + if (unlikely(m_parentSurf != nullptr && m_commonSurf->IsDepthStencil() + && m_parentSurf->GetAttachedDepthStencil() == this)) { + m_parentSurf->ClearAttachedDepthStencil(); + } + + m_commonIntf->RemoveWrappedSurface(this); + + m_commonSurf->SetDD2Surface(nullptr); + + Logger::debug(str::format("DDraw2Surface: Surface nr. [[2-", m_surfCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDraw2Surface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (riid == __uuidof(IDirect3DTexture2)) { + Logger::debug("DDraw2Surface::QueryInterface: Query for IDirect3DTexture2"); + + if (unlikely(m_parent->GetD3D5Texture() == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + D3DTEXTUREHANDLE nextHandle = m_commonIntf->GetNextTextureHandle(); + Com texture5 = new D3D5Texture(std::move(ppvProxyObject), m_parent, nextHandle); + D3DCommonTexture* commonTex = texture5->GetCommonTexture(); + m_parent->SetD3D5Texture(texture5.ptr()); + m_commonIntf->EmplaceTexture(commonTex, nextHandle); + } + + *ppvObject = ref(m_parent->GetD3D5Texture()); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirect3DTexture))) { + Logger::debug("DDraw2Surface::QueryInterface: Query for IDirect3DTexture"); + + if (unlikely(m_parent->GetD3D3Texture() == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + D3DTEXTUREHANDLE nextHandle = m_commonIntf->GetNextTextureHandle(); + Com texture3 = new D3D3Texture(std::move(ppvProxyObject), m_parent, nextHandle); + D3DCommonTexture* commonTex = texture3->GetCommonTexture(); + m_parent->SetD3D3Texture(texture3.ptr()); + m_commonIntf->EmplaceTexture(commonTex, nextHandle); + } + + *ppvObject = ref(m_parent->GetD3D3Texture()); + + return S_OK; + } + if (riid == __uuidof(IDirectDrawGammaControl)) { + Logger::debug("DDraw2Surface::QueryInterface: Query for IDirectDrawGammaControl"); + void* gammaControlProxiedVoid = nullptr; + // This can never reasonably fail + m_proxy->QueryInterface(__uuidof(IDirectDrawGammaControl), &gammaControlProxiedVoid); + Com gammaControlProxied = static_cast(gammaControlProxiedVoid); + *ppvObject = ref(new DDrawGammaControl(m_commonSurf.ptr(), std::move(gammaControlProxied), m_commonSurf->GetDDSurface())); + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawColorControl))) { + Logger::debug("DDraw2Surface::QueryInterface: Query for IDirectDrawColorControl"); + return E_NOINTERFACE; + } + if (unlikely(riid == __uuidof(IUnknown) + || riid == __uuidof(IDirectDrawSurface))) { + if (m_commonSurf->GetDDSurface() != nullptr) { + Logger::debug("DDraw2Surface::QueryInterface: Query for existing IDirectDrawSurface"); + return m_commonSurf->GetDDSurface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Surface::QueryInterface: Query for IDirectDrawSurface"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDrawSurface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDDInterface(), nullptr, false)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface3))) { + if (m_commonSurf->GetDD3Surface() != nullptr) { + Logger::debug("DDraw2Surface::QueryInterface: Query for existing IDirectDrawSurface3"); + return m_commonSurf->GetDD3Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Surface::QueryInterface: Query for IDirectDrawSurface3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw3Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonSurf->GetDDSurface(), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface4))) { + if (m_commonSurf->GetDD4Surface() != nullptr) { + Logger::debug("DDraw2Surface::QueryInterface: Query for existing IDirectDrawSurface4"); + return m_commonSurf->GetDD4Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Surface::QueryInterface: Query for IDirectDrawSurface4"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw4Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDD4Interface(), nullptr, false)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface7))) { + if (m_commonSurf->GetDD7Surface() != nullptr) { + Logger::debug("DDraw2Surface::QueryInterface: Query for existing IDirectDrawSurface7"); + return m_commonSurf->GetDD7Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw2Surface::QueryInterface: Query for IDirectDrawSurface7"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw7Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDD7Interface(), nullptr, false)); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::AddAttachedSurface(LPDIRECTDRAWSURFACE2 lpDDSAttachedSurface) { + Logger::debug("<<< DDraw2Surface::AddAttachedSurface: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + Logger::warn("DDraw2Surface::AddAttachedSurface: Received an unwrapped surface"); + return DDERR_CANNOTATTACHSURFACE; + } + + DDraw2Surface* ddraw2Surface = static_cast(lpDDSAttachedSurface); + + if (unlikely(ddraw2Surface->GetCommonSurface()->IsBackBufferOrFlippable())) + Logger::warn("DDraw2Surface::AddAttachedSurface: Trying to attach a flippable surface"); + + HRESULT hr = m_proxy->AddAttachedSurface(ddraw2Surface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + ddraw2Surface->SetParentSurface(this); + if (likely(ddraw2Surface->GetCommonSurface()->IsDepthStencil())) + m_depthStencil = ddraw2Surface; + + return hr; + } + + // Docs: "This method is used for the software implementation. + // It is not needed if the overlay support is provided by the hardware." + HRESULT STDMETHODCALLTYPE DDraw2Surface::AddOverlayDirtyRect(LPRECT lpRect) { + Logger::debug(">>> DDraw2Surface::AddOverlayDirtyRect"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE2 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) { + Logger::debug("<<< DDraw2Surface::Blt: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDraw2Surface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw2Surface::Blt: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(m_parent->GetProxied(), m_parent->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw2Surface::Blt: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw2Surface::Blt: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(m_parent->GetProxied(), m_parent->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw2Surface::Blt: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + // Forward DDBLT_DEPTHFILL clears to D3D9 if done on the current depth stencil + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_DEPTHFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9DepthStencil(m_d3d9.ptr()))) { + Logger::debug("DDraw2Surface::Blt: Clearing d3d9 depth stencil"); + + HRESULT hrClear; + const float zClear = m_commonSurf->GetNormalizedFloatDepth(lpDDBltFx->dwFillDepth); + + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0, zClear, 256); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_ZBUFFER, 0, zClear, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDraw2Surface::Blt: Failed to clear d3d9 depth"); + } + // Forward DDBLT_COLORFILL clears to D3D9 if done on the current render target + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_COLORFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9RenderTarget(m_d3d9.ptr()))) { + Logger::debug("DDraw2Surface::Blt: Clearing d3d9 render target"); + + HRESULT hrClear; + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDraw2Surface::Blt: Failed to clear d3d9 render target"); + } + + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDraw2Surface::Blt: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->Blt(lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFx); + } else { + DDraw2Surface* ddraw2Surface = static_cast(lpDDSrcSurface); + hr = m_proxy->Blt(lpDestRect, ddraw2Surface->GetProxied(), lpSrcRect, dwFlags, lpDDBltFx); + } + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw2Surface::Blt: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + // Docs: "The IDirectDrawSurface2::BltBatch method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw2Surface::BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) { + Logger::debug(">>> DDraw2Surface::BltBatch"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE2 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) { + Logger::debug("<<< DDraw2Surface::BltFast: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDraw2Surface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw2Surface::BltFast: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(m_parent->GetProxied(), m_parent->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw2Surface::BltFast: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw2Surface::BltFast: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(m_parent->GetProxied(), m_parent->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw2Surface::BltFast: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDraw2Surface::BltFast: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->BltFast(dwX, dwY, lpDDSrcSurface, lpSrcRect, dwTrans); + } else { + DDraw2Surface* ddraw2Surface = static_cast(lpDDSrcSurface); + hr = m_proxy->BltFast(dwX, dwY, ddraw2Surface->GetProxied(), lpSrcRect, dwTrans); + } + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw2Surface::BltFast: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE2 lpDDSAttachedSurface) { + Logger::debug("<<< DDraw2Surface::DeleteAttachedSurface: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + if (unlikely(lpDDSAttachedSurface != nullptr)) { + Logger::warn("DDraw2Surface::DeleteAttachedSurface: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + HRESULT hrProxy = m_proxy->DeleteAttachedSurface(dwFlags, lpDDSAttachedSurface); + + // If lpDDSAttachedSurface is NULL, then all surfaces are detached + if (lpDDSAttachedSurface == nullptr && likely(SUCCEEDED(hrProxy))) + m_depthStencil = nullptr; + + return hrProxy; + } + + DDraw2Surface* ddraw2Surface = static_cast(lpDDSAttachedSurface); + + HRESULT hr = m_proxy->DeleteAttachedSurface(dwFlags, ddraw2Surface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + if (likely(m_depthStencil == ddraw2Surface)) { + ddraw2Surface->ClearParentSurface(); + m_depthStencil = nullptr; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) { + Logger::warn(">>> DDraw2Surface::EnumAttachedSurfaces"); + return m_parent->EnumAttachedSurfaces(lpContext, lpEnumSurfacesCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback) { + Logger::debug("<<< DDraw2Surface::EnumOverlayZOrders: Proxy"); + return m_proxy->EnumOverlayZOrders(dwFlags, lpContext, lpfnCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::Flip(LPDIRECTDRAWSURFACE2 lpDDSurfaceTargetOverride, DWORD dwFlags) { + // Lost surfaces are not flippable + HRESULT hr = m_proxy->IsLost(); + if (unlikely(FAILED(hr))) { + Logger::debug("DDraw2Surface::Flip: Lost surface"); + return hr; + } + + if (unlikely(!(m_commonSurf->IsFrontBuffer() || m_commonSurf->IsBackBufferOrFlippable()))) { + Logger::debug("DDraw2Surface::Flip: Unflippable surface"); + return DDERR_NOTFLIPPABLE; + } + + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Non-exclusive mode validations + if (unlikely(m_commonSurf->IsPrimarySurface() && !exclusiveMode)) { + Logger::debug("DDraw2Surface::Flip: Primary surface flip in non-exclusive mode"); + return DDERR_NOEXCLUSIVEMODE; + } + + // Exclusive mode validations + if (unlikely(m_commonSurf->IsBackBufferOrFlippable() && exclusiveMode)) { + Logger::debug("DDraw2Surface::Flip: Back buffer flip in exclusive mode"); + return DDERR_NOTFLIPPABLE; + } + + Com surf2 = static_cast(lpDDSurfaceTargetOverride); + if (lpDDSurfaceTargetOverride != nullptr) { + if (unlikely(!surf2->GetParent()->GetCommonSurface()->IsBackBufferOrFlippable())) { + Logger::debug("DDraw2Surface::Flip: Unflippable override surface"); + return DDERR_NOTFLIPPABLE; + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + Logger::debug("*** DDraw2Surface::Flip: Presenting"); + + m_commonIntf->ResetDrawTracking(); + + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(!IsInitialized())) + m_parent->InitializeD3D9(m_commonIntf->IsCurrentRenderTarget(m_parent)); + + BlitToDDrawSurface(m_proxy.ptr(), m_parent->GetD3D9()); + + if (likely(m_commonIntf->IsCurrentRenderTarget(m_parent))) { + if (lpDDSurfaceTargetOverride != nullptr) { + m_commonIntf->SetFlipRTSurfaceAndFlags(surf2->GetParent()->GetProxied(), dwFlags); + } else { + m_commonIntf->SetFlipRTSurfaceAndFlags(nullptr, dwFlags); + } + } + if (lpDDSurfaceTargetOverride != nullptr) { + return m_proxy->Flip(surf2->GetProxied(), dwFlags); + } else { + return m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } + } + + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + // If we don't have a valid D3D5 device, this means a D3D3 application + // is trying to flip the surface. Allow that for compatibility reasons. + } else { + Logger::debug("<<< DDraw2Surface::Flip: Proxy"); + if (lpDDSurfaceTargetOverride == nullptr) { + m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } else { + m_proxy->Flip(surf2->GetProxied(), dwFlags); + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetAttachedSurface(LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE2 *lplpDDAttachedSurface) { + Logger::debug("<<< DDraw2Surface::GetAttachedSurface: Proxy"); + + if (unlikely(lpDDSCaps == nullptr || lplpDDAttachedSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (lpDDSCaps->dwCaps & DDSCAPS_PRIMARYSURFACE) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for the primary surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FRONTBUFFER) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for the front buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_BACKBUFFER) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for the back buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FLIP) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for a flippable surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OFFSCREENPLAIN) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for an offscreen plain surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_ZBUFFER) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for a depth stencil"); + else if (lpDDSCaps->dwCaps & DDSCAPS_MIPMAP) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for a texture mip map"); + else if (lpDDSCaps->dwCaps & DDSCAPS_TEXTURE) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for a texture"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OVERLAY) + Logger::debug("DDraw2Surface::GetAttachedSurface: Querying for an overlay"); + + Com surface; + HRESULT hr = m_proxy->GetAttachedSurface(lpDDSCaps, &surface); + + // These are rather common, as some games query expecting to get nothing in return, for + // example it's a common use case to query the mip attach chain until nothing is returned + if (FAILED(hr)) { + Logger::debug("DDraw2Surface::GetAttachedSurface: Failed to find the requested surface"); + *lplpDDAttachedSurface = surface.ptr(); + return hr; + } + + try { + auto attachedSurfaceIter = m_attachedSurfaces.find(surface.ptr()); + if (unlikely(attachedSurfaceIter == m_attachedSurfaces.end())) { + // Return the already attached depth surface if it exists + if (unlikely(m_depthStencil != nullptr && surface.ptr() == m_depthStencil->GetProxied())) { + *lplpDDAttachedSurface = m_depthStencil.ref(); + } else { + Com ddraw2Surface = new DDraw2Surface(nullptr, std::move(surface), nullptr, this); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(ddraw2Surface->GetProxied()), + std::forward_as_tuple(ddraw2Surface.ref())); + *lplpDDAttachedSurface = ddraw2Surface.ref(); + } + } else { + *lplpDDAttachedSurface = attachedSurfaceIter->second.ref(); + } + } catch (const DxvkError& e) { + Logger::err(e.message()); + *lplpDDAttachedSurface = nullptr; + return DDERR_GENERIC; + } + + return DD_OK; + } + + // Blitting can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetBltStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw2Surface::GetBltStatus: Proxy"); + m_proxy->GetBltStatus(dwFlags); + } + + Logger::debug(">>> DDraw2Surface::GetBltStatus"); + + if (likely(dwFlags == DDGBS_CANBLT || dwFlags == DDGBS_ISBLTDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetCaps(LPDDSCAPS lpDDSCaps) { + Logger::debug(">>> DDraw2Surface::GetCaps"); + + if (unlikely(lpDDSCaps == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDSCaps = m_commonSurf->GetDesc()->ddsCaps; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper) { + Logger::debug(">>> DDraw2Surface::GetClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + DDrawClipper* clipper = m_commonSurf->GetClipper(); + + if (unlikely(clipper == nullptr)) + return DDERR_NOCLIPPERATTACHED; + + *lplpDDClipper = ref(clipper); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDraw2Surface::GetColorKey: Proxy"); + return m_proxy->GetColorKey(dwFlags, lpDDColorKey); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetDC(HDC *lphDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(lphDC == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lphDC); + + // Foward GetDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDraw2Surface::GetDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw2Surface::GetDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_parent->GetD3D9()->GetDC(lphDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDraw2Surface::GetDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDraw2Surface::GetDC: Proxy"); + return m_proxy->GetDC(lphDC); + } + + // Flipping can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetFlipStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw2Surface::GetFlipStatus: Proxy"); + m_proxy->GetFlipStatus(dwFlags); + } + + Logger::debug(">>> DDraw2Surface::GetFlipStatus"); + + if (likely(dwFlags == DDGFS_CANFLIP || dwFlags == DDGFS_ISFLIPDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetOverlayPosition(LPLONG lplX, LPLONG lplY) { + Logger::debug("<<< DDraw2Surface::GetOverlayPosition: Proxy"); + return m_proxy->GetOverlayPosition(lplX, lplY); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette) { + Logger::debug(">>> DDraw2Surface::GetPalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + DDrawPalette* palette = m_commonSurf->GetPalette(); + + if (unlikely(palette == nullptr)) + return DDERR_NOPALETTEATTACHED; + + *lplpDDPalette = ref(palette); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat) { + Logger::debug(">>> DDraw2Surface::GetPixelFormat"); + + if (unlikely(lpDDPixelFormat == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDPixelFormat = m_commonSurf->GetDesc()->ddpfPixelFormat; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc) { + Logger::debug(">>> DDraw2Surface::GetSurfaceDesc"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpDDSurfaceDesc->dwSize != sizeof(DDSURFACEDESC))) + return DDERR_INVALIDPARAMS; + + *lpDDSurfaceDesc = *m_commonSurf->GetDesc(); + + return DD_OK; + } + + // According to the docs: "Because the DirectDrawSurface object is initialized + // when it's created, this method always returns DDERR_ALREADYINITIALIZED." + HRESULT STDMETHODCALLTYPE DDraw2Surface::Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc) { + Logger::debug(">>> DDraw2Surface::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::IsLost() { + Logger::debug("<<< DDraw2Surface::IsLost: Proxy"); + return m_proxy->IsLost(); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::Lock(LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) { + Logger::debug("<<< DDraw2Surface::Lock: Proxy"); + + // It's highly unlikely anyone would do depth locks with IDirectDrawSurface2 + if (unlikely(m_commonSurf->IsDepthStencil())) { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw2Surface::Lock: Surface is a depth stencil"); + } + + return m_proxy->Lock(lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::ReleaseDC(HDC hDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + // Foward ReleaseDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDraw2Surface::ReleaseDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw2Surface::ReleaseDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_parent->GetD3D9()->ReleaseDC(hDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDraw2Surface::ReleaseDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDraw2Surface::ReleaseDC: Proxy"); + + HRESULT hr = m_proxy->ReleaseDC(hDC); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (m_commonSurf->IsTexture()) { + m_commonSurf->DirtyMipMaps(); + } else if (unlikely(m_commonIntf->GetOptions()->apitraceMode)) { + // We should ideally upload the surface contents here at all times, + // however some games are amazing, and do hundreds of locks on the same + // surface per frame, so this would absolutely tank performance + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw2Surface::ReleaseDC: Failed upload to d3d9 surface"); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::Restore() { + Logger::debug("<<< DDraw2Surface::Restore: Proxy"); + return m_proxy->Restore(); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper) { + Logger::debug("<<< DDraw2Surface::SetClipper: Proxy"); + + // A nullptr lpDDClipper gets the current clipper detached + if (lpDDClipper == nullptr) { + HRESULT hr = m_proxy->SetClipper(lpDDClipper); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(nullptr); + } else { + DDrawClipper* ddrawClipper = static_cast(lpDDClipper); + + HRESULT hr = m_proxy->SetClipper(ddrawClipper->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(ddrawClipper); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDraw2Surface::SetColorKey: Proxy"); + + HRESULT hr = m_proxy->SetColorKey(dwFlags, lpDDColorKey); + if (unlikely(FAILED(hr))) + return hr; + + hr = m_commonSurf->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::err("DDraw2Surface::SetColorKey: Failed to retrieve updated surface desc"); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::SetOverlayPosition(LONG lX, LONG lY) { + Logger::debug("<<< DDraw2Surface::SetOverlayPosition: Proxy"); + return m_proxy->SetOverlayPosition(lX, lY); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) { + Logger::debug("<<< DDraw2Surface::SetPalette: Proxy"); + + // A nullptr lpDDPalette gets the current palette detached + if (lpDDPalette == nullptr) { + HRESULT hr = m_proxy->SetPalette(lpDDPalette); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(nullptr); + } else { + DDrawPalette* ddrawPalette = static_cast(lpDDPalette); + + HRESULT hr = m_proxy->SetPalette(ddrawPalette->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(ddrawPalette); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::Unlock(LPVOID lpSurfaceData) { + Logger::debug("<<< DDraw2Surface::Unlock: Proxy"); + + HRESULT hr = m_proxy->Unlock(lpSurfaceData); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw2Surface::Unlock: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE2 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) { + Logger::debug("<<< DDraw2Surface::UpdateOverlay: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDDestSurface))) { + Logger::warn("DDraw2Surface::UpdateOverlay: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw2Surface* ddraw2Surface = static_cast(lpDDDestSurface); + return m_proxy->UpdateOverlay(lpSrcRect, ddraw2Surface->GetProxied(), lpDestRect, dwFlags, lpDDOverlayFx); + } + + // Docs: "This method is for software emulation only; it does nothing if the hardware supports overlays." + HRESULT STDMETHODCALLTYPE DDraw2Surface::UpdateOverlayDisplay(DWORD dwFlags) { + Logger::debug(">>> DDraw2Surface::UpdateOverlayDisplay"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE2 lpDDSReference) { + Logger::debug("<<< DDraw2Surface::UpdateOverlayZOrder: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSReference))) { + Logger::warn("DDraw2Surface::UpdateOverlayZOrder: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw2Surface* ddraw2Surface = static_cast(lpDDSReference); + return m_proxy->UpdateOverlayZOrder(dwFlags, ddraw2Surface->GetProxied()); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::GetDDInterface(LPVOID *lplpDD) { + if (unlikely(m_commonIntf->GetDDInterface() == nullptr)) { + Logger::warn("<<< DDraw2Surface::GetDDInterface: Proxy"); + return m_proxy->GetDDInterface(lplpDD); + } + + Logger::debug(">>> DDraw2Surface::GetDDInterface"); + + if (unlikely(lplpDD == nullptr)) + return DDERR_INVALIDPARAMS; + + // Was an easy footgun to return a proxied interface + *lplpDD = ref(m_commonIntf->GetDDInterface()); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::PageLock(DWORD dwFlags) { + Logger::debug("<<< DDraw2Surface::PageLock: Proxy"); + return m_proxy->PageLock(dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDraw2Surface::PageUnlock(DWORD dwFlags) { + Logger::debug("<<< DDraw2Surface::PageUnlock: Proxy"); + return m_proxy->PageUnlock(dwFlags); + } + +} diff --git a/src/ddraw/ddraw2/ddraw2_surface.h b/src/ddraw/ddraw2/ddraw2_surface.h new file mode 100644 index 00000000000..e856c5c4a13 --- /dev/null +++ b/src/ddraw/ddraw2/ddraw2_surface.h @@ -0,0 +1,196 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../ddraw_common_surface.h" + +#include "../ddraw/ddraw_surface.h" + +#include + +namespace dxvk { + + class DDraw7Surface; + + /** + * \brief IDirectDrawSurface2 interface implementation + */ + class DDraw2Surface final : public DDrawWrappedObject { + + public: + + DDraw2Surface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDrawSurface* pParent, + DDraw2Surface* pParentSurf); + + ~DDraw2Surface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE AddAttachedSurface(LPDIRECTDRAWSURFACE2 lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE AddOverlayDirtyRect(LPRECT lpRect); + + HRESULT STDMETHODCALLTYPE Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE2 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx); + + HRESULT STDMETHODCALLTYPE BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE2 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans); + + HRESULT STDMETHODCALLTYPE DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE2 lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback); + + HRESULT STDMETHODCALLTYPE Flip(LPDIRECTDRAWSURFACE2 lpDDSurfaceTargetOverride, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetAttachedSurface(LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE2 *lplpDDAttachedSurface); + + HRESULT STDMETHODCALLTYPE GetBltStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDSCAPS lpDDSCaps); + + HRESULT STDMETHODCALLTYPE GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper); + + HRESULT STDMETHODCALLTYPE GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE GetDC(HDC *lphDC); + + HRESULT STDMETHODCALLTYPE GetFlipStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetOverlayPosition(LPLONG lplX, LPLONG lplY); + + HRESULT STDMETHODCALLTYPE GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette); + + HRESULT STDMETHODCALLTYPE GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat); + + HRESULT STDMETHODCALLTYPE GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE IsLost(); + + HRESULT STDMETHODCALLTYPE Lock(LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent); + + HRESULT STDMETHODCALLTYPE ReleaseDC(HDC hDC); + + HRESULT STDMETHODCALLTYPE Restore(); + + HRESULT STDMETHODCALLTYPE SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper); + + HRESULT STDMETHODCALLTYPE SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE SetOverlayPosition(LONG lX, LONG lY); + + HRESULT STDMETHODCALLTYPE SetPalette(LPDIRECTDRAWPALETTE lpDDPalette); + + HRESULT STDMETHODCALLTYPE Unlock(LPVOID lpSurfaceData); + + HRESULT STDMETHODCALLTYPE UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE2 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx); + + HRESULT STDMETHODCALLTYPE UpdateOverlayDisplay(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE2 lpDDSReference); + + HRESULT STDMETHODCALLTYPE GetDDInterface(LPVOID *lplpDD); + + HRESULT STDMETHODCALLTYPE PageLock(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE PageUnlock(DWORD dwFlags); + + DDrawCommonSurface* GetCommonSurface() const { + return m_commonSurf.ptr(); + } + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + d3d9::IDirect3DDevice9* GetD3D9Device() const { + return m_d3d9Device; + } + + DDraw2Surface* GetAttachedDepthStencil() { + // Fast path, since in most cases we already store the required surface + if (likely(m_depthStencil.ptr() != nullptr)) + return m_depthStencil.ptr(); + + DDSCAPS caps; + caps.dwCaps = DDSCAPS_ZBUFFER; + IDirectDrawSurface2* surface = nullptr; + HRESULT hr = GetAttachedSurface(&caps, &surface); + if (unlikely(FAILED(hr))) + return nullptr; + + m_depthStencil = reinterpret_cast(surface); + + return m_depthStencil.ptr(); + } + + void ClearAttachedDepthStencil() { + m_depthStencil = nullptr; + } + + void SetParentSurface(DDraw2Surface* surface) { + m_parentSurf = surface; + m_commonSurf->SetIsAttached(true); + } + + void ClearParentSurface() { + m_parentSurf = nullptr; + m_commonSurf->SetIsAttached(false); + } + + HRESULT InitializeOrUploadD3D9() { + return m_parent->InitializeOrUploadD3D9(); + } + + bool IsInitialized() { + return m_parent->IsInitialized(); + } + + private: + + inline void RefreshD3D9Device() { + if (likely(m_parent != nullptr)) { + d3d9::IDirect3DDevice9* d3d9Device = m_parent->GetCommonInterface()->GetD3D9Device(); + if (unlikely(m_d3d9Device != d3d9Device)) { + // Check if the device has been recreated and reset all D3D9 resources + if (m_d3d9Device != nullptr) { + Logger::debug("DDrawSurface: Device context has changed, clearing all D3D9 resources"); + m_d3d9 = nullptr; + } + m_d3d9Device = d3d9Device; + } + } + } + + static uint32_t s_surfCount; + uint32_t m_surfCount = 0; + + Com m_commonSurf; + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_originSurf; + + DDraw2Surface* m_parentSurf = nullptr; + + d3d9::IDirect3DDevice9* m_d3d9Device = nullptr; + + // Back buffers will have depth stencil surfaces as attachments (in practice + // I have never seen more than one depth stencil being attached at a time) + Com m_depthStencil; + + // These are attached surfaces, which are typically mips or other types of generated + // surfaces, which need to exist for the entire lifecycle of their parent surface. + // They are implemented with linked list, so for example only one mip level + // will be held in a parent texture, and the next mip level will be held in the previous mip. + std::unordered_map> m_attachedSurfaces; + + }; + +} diff --git a/src/ddraw/ddraw2/ddraw3_surface.cpp b/src/ddraw/ddraw2/ddraw3_surface.cpp new file mode 100644 index 00000000000..12fa820a587 --- /dev/null +++ b/src/ddraw/ddraw2/ddraw3_surface.cpp @@ -0,0 +1,1045 @@ +#include "ddraw3_surface.h" + +#include "../ddraw_gamma.h" + +#include "../ddraw/ddraw_interface.h" +#include "../ddraw/ddraw_surface.h" +#include "../ddraw2/ddraw2_surface.h" +#include "../ddraw4/ddraw4_surface.h" +#include "../ddraw7/ddraw7_surface.h" + +#include "../d3d3/d3d3_texture.h" +#include "../d3d5/d3d5_device.h" +#include +#include "../d3d5/d3d5_texture.h" + +namespace dxvk { + + uint32_t DDraw3Surface::s_surfCount = 0; + + DDraw3Surface::DDraw3Surface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDrawSurface* pParent, + DDraw3Surface* pParentSurf) + : DDrawWrappedObject(pParent, std::move(surfProxy), nullptr) + , m_commonSurf ( commonSurf ) + , m_parentSurf ( pParentSurf ) { + if (m_parent != nullptr) { + m_commonIntf = m_parent->GetCommonInterface(); + } else if (m_parentSurf != nullptr) { + m_commonIntf = m_parentSurf->GetCommonInterface(); + } else if (m_commonSurf != nullptr) { + m_commonIntf = m_commonSurf->GetCommonInterface(); + } else { + throw DxvkError("DDraw3Surface: ERROR! Failed to retrieve the common interface!"); + } + + if (m_commonSurf == nullptr) + m_commonSurf = new DDrawCommonSurface(m_commonIntf); + + // Retrieve and cache the proxy surface desc + if (unlikely(!m_commonSurf->IsDescSet())) { + DDSURFACEDESC desc; + desc.dwSize = sizeof(DDSURFACEDESC); + HRESULT hr = m_proxy->GetSurfaceDesc(&desc); + + if (unlikely(FAILED(hr))) { + throw DxvkError("DDraw3Surface: ERROR! Failed to retrieve new surface desc!"); + } else { + m_commonSurf->SetDesc(desc); + } + } + + // We need to keep the IDirectDrawSurface parent around, since we're entirely dependent on it. + // If one doesn't exist (e.g. attached surfaces), then use QueryInterface and cache it. + if (m_parent == nullptr) { + Com originSurf; + HRESULT hr = m_proxy->QueryInterface(__uuidof(IDirectDrawSurface), reinterpret_cast(&originSurf)); + if (unlikely(FAILED(hr))) + throw DxvkError("DDraw3Surface: ERROR! Failed to retrieve an IDirectDrawSurface interface!"); + + m_originSurf = new DDrawSurface(m_commonSurf.ptr(), std::move(originSurf), m_commonIntf->GetDDInterface(), nullptr, false); + m_parent = m_originSurf.ptr(); + } else { + m_originSurf = m_parent; + } + + m_commonIntf->AddWrappedSurface(this); + + m_commonSurf->SetDD3Surface(this); + + m_surfCount = ++s_surfCount; + + Logger::debug(str::format("DDraw3Surface: Created a new surface nr. [[3-", m_surfCount, "]]")); + + // Note: IDirectDrawSurface3 can't ever be the origin surface + } + + DDraw3Surface::~DDraw3Surface() { + // Clear the cached depth stencil on the parent if matched + if (unlikely(m_parentSurf != nullptr && m_commonSurf->IsDepthStencil() + && m_parentSurf->GetAttachedDepthStencil() == this)) { + m_parentSurf->ClearAttachedDepthStencil(); + } + + m_commonIntf->RemoveWrappedSurface(this); + + m_commonSurf->SetDD3Surface(nullptr); + + Logger::debug(str::format("DDraw3Surface: Surface nr. [[3-", m_surfCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDraw3Surface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (riid == __uuidof(IDirect3DTexture2)) { + Logger::debug("DDraw3Surface::QueryInterface: Query for IDirect3DTexture2"); + + if (unlikely(m_parent->GetD3D5Texture() == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + D3DTEXTUREHANDLE nextHandle = m_commonIntf->GetNextTextureHandle(); + Com texture5 = new D3D5Texture(std::move(ppvProxyObject), m_parent, nextHandle); + D3DCommonTexture* commonTex = texture5->GetCommonTexture(); + m_parent->SetD3D5Texture(texture5.ptr()); + m_commonIntf->EmplaceTexture(commonTex, nextHandle); + } + + *ppvObject = ref(m_parent->GetD3D5Texture()); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirect3DTexture))) { + Logger::debug("DDraw3Surface::QueryInterface: Query for IDirect3DTexture"); + + if (unlikely(m_parent->GetD3D3Texture() == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + D3DTEXTUREHANDLE nextHandle = m_commonIntf->GetNextTextureHandle(); + Com texture3 = new D3D3Texture(std::move(ppvProxyObject), m_parent, nextHandle); + D3DCommonTexture* commonTex = texture3->GetCommonTexture(); + m_parent->SetD3D3Texture(texture3.ptr()); + m_commonIntf->EmplaceTexture(commonTex, nextHandle); + } + + *ppvObject = ref(m_parent->GetD3D3Texture()); + + return S_OK; + } + if (riid == __uuidof(IDirectDrawGammaControl)) { + Logger::debug("DDraw3Surface::QueryInterface: Query for IDirectDrawGammaControl"); + void* gammaControlProxiedVoid = nullptr; + // This can never reasonably fail + m_proxy->QueryInterface(__uuidof(IDirectDrawGammaControl), &gammaControlProxiedVoid); + Com gammaControlProxied = static_cast(gammaControlProxiedVoid); + *ppvObject = ref(new DDrawGammaControl(m_commonSurf.ptr(), std::move(gammaControlProxied), m_commonSurf->GetDDSurface())); + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawColorControl))) { + Logger::debug("DDraw3Surface::QueryInterface: Query for IDirectDrawColorControl"); + return E_NOINTERFACE; + } + if (unlikely(riid == __uuidof(IUnknown) + || riid == __uuidof(IDirectDrawSurface))) { + if (m_commonSurf->GetDDSurface() != nullptr) { + Logger::debug("DDraw3Surface::QueryInterface: Query for existing IDirectDrawSurface"); + return m_commonSurf->GetDDSurface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw3Surface::QueryInterface: Query for IDirectDrawSurface"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDrawSurface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDDInterface(), nullptr, false)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface2))) { + if (m_commonSurf->GetDD2Surface() != nullptr) { + Logger::debug("DDraw3Surface::QueryInterface: Query for existing IDirectDrawSurface2"); + return m_commonSurf->GetDD2Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw3Surface::QueryInterface: Query for IDirectDrawSurface3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw2Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonSurf->GetDDSurface(), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface4))) { + if (m_commonSurf->GetDD4Surface() != nullptr) { + Logger::debug("DDraw3Surface::QueryInterface: Query for existing IDirectDrawSurface4"); + return m_commonSurf->GetDD4Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw3Surface::QueryInterface: Query for IDirectDrawSurface4"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw4Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDD4Interface(), nullptr, false)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface7))) { + if (m_commonSurf->GetDD7Surface() != nullptr) { + Logger::debug("DDraw3Surface::QueryInterface: Query for existing IDirectDrawSurface7"); + return m_commonSurf->GetDD7Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw3Surface::QueryInterface: Query for IDirectDrawSurface7"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw7Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDD7Interface(), nullptr, false)); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::AddAttachedSurface(LPDIRECTDRAWSURFACE3 lpDDSAttachedSurface) { + Logger::debug("<<< DDraw3Surface::AddAttachedSurface: Proxy"); + + // Some games, like Nightmare Creatures, try to attach an IDirectDrawSurface depth stencil directly... + if (unlikely(m_commonIntf->IsWrappedSurface(reinterpret_cast(lpDDSAttachedSurface)))) { + Logger::debug("DDraw3Surface::AddAttachedSurface: Attaching IDirectDrawSurface surface"); + DDrawSurface* ddrawSurface = reinterpret_cast(lpDDSAttachedSurface); + Com surface3; + ddrawSurface->GetProxied()->QueryInterface(__uuidof(IDirectDrawSurface3), reinterpret_cast(&surface3)); + return m_proxy->AddAttachedSurface(surface3.ptr()); + } + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + Logger::warn("DDraw3Surface::AddAttachedSurface: Received an unwrapped surface"); + return DDERR_CANNOTATTACHSURFACE; + } + + DDraw3Surface* ddraw3Surface = static_cast(lpDDSAttachedSurface); + + if (unlikely(ddraw3Surface->GetCommonSurface()->IsBackBufferOrFlippable())) + Logger::warn("DDraw3Surface::AddAttachedSurface: Trying to attach a flippable surface"); + + HRESULT hr = m_proxy->AddAttachedSurface(ddraw3Surface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + ddraw3Surface->SetParentSurface(this); + if (likely(ddraw3Surface->GetCommonSurface()->IsDepthStencil())) + m_depthStencil = ddraw3Surface; + + return hr; + } + + // Docs: "This method is used for the software implementation. + // It is not needed if the overlay support is provided by the hardware." + HRESULT STDMETHODCALLTYPE DDraw3Surface::AddOverlayDirtyRect(LPRECT lpRect) { + Logger::debug(">>> DDraw3Surface::AddOverlayDirtyRect"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE3 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) { + Logger::debug("<<< DDraw3Surface::Blt: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDraw3Surface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + // Tomb Raider 3 relies on back buffer write backs using iDirectDrawSurface3 surfaces + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw3Surface::Blt: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(m_parent->GetProxied(), m_parent->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw3Surface::Blt: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw3Surface::Blt: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(m_parent->GetProxied(), m_parent->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw3Surface::Blt: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + // Forward DDBLT_DEPTHFILL clears to D3D9 if done on the current depth stencil + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_DEPTHFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9DepthStencil(m_d3d9.ptr()))) { + Logger::debug("DDraw3Surface::Blt: Clearing d3d9 depth stencil"); + + HRESULT hrClear; + const float zClear = m_commonSurf->GetNormalizedFloatDepth(lpDDBltFx->dwFillDepth); + + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0, zClear, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_ZBUFFER, 0, zClear, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDraw3Surface::Blt: Failed to clear d3d9 depth"); + } + // Forward DDBLT_COLORFILL clears to D3D9 if done on the current render target + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_COLORFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9RenderTarget(m_d3d9.ptr()))) { + Logger::debug("DDraw3Surface::Blt: Clearing d3d9 render target"); + + HRESULT hrClear; + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDraw3Surface::Blt: Failed to clear d3d9 render target"); + } + + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + // Powerslide tries to blit here from a IDirectDrawSurface source + if (unlikely(m_commonIntf->IsWrappedSurface(reinterpret_cast(lpDDSrcSurface)))) { + Logger::debug("DDraw3Surface::Blt: Received an IDirectDrawSurface source surface"); + DDrawSurface* ddrawSurface = reinterpret_cast(lpDDSrcSurface); + Com surface3; + ddrawSurface->GetProxied()->QueryInterface(__uuidof(IDirectDrawSurface3), reinterpret_cast(&surface3)); + hr = m_proxy->Blt(lpDestRect, surface3.ptr(), lpSrcRect, dwFlags, lpDDBltFx); + } else if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDraw3Surface::Blt: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->Blt(lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFx); + } else { + DDraw3Surface* ddraw3Surface = static_cast(lpDDSrcSurface); + hr = m_proxy->Blt(lpDestRect, ddraw3Surface->GetProxied(), lpSrcRect, dwFlags, lpDDBltFx); + } + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw3Surface::Blt: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + // Docs: "The IDirectDrawSurface3::BltBatch method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw3Surface::BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) { + Logger::debug(">>> DDraw3Surface::BltBatch"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE3 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) { + Logger::debug("<<< DDraw3Surface::BltFast: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDraw3Surface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw3Surface::BltFast: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(m_parent->GetProxied(), m_parent->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw3Surface::BltFast: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw3Surface::BltFast: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(m_parent->GetProxied(), m_parent->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw3Surface::BltFast: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDraw3Surface::BltFast: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->BltFast(dwX, dwY, lpDDSrcSurface, lpSrcRect, dwTrans); + } else { + DDraw3Surface* ddraw3Surface = static_cast(lpDDSrcSurface); + hr = m_proxy->BltFast(dwX, dwY, ddraw3Surface->GetProxied(), lpSrcRect, dwTrans); + } + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw3Surface::BltFast: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE3 lpDDSAttachedSurface) { + Logger::debug("<<< DDraw3Surface::DeleteAttachedSurface: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + if (unlikely(lpDDSAttachedSurface != nullptr)) { + Logger::warn("DDraw3Surface::DeleteAttachedSurface: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + HRESULT hrProxy = m_proxy->DeleteAttachedSurface(dwFlags, lpDDSAttachedSurface); + + // If lpDDSAttachedSurface is NULL, then all surfaces are detached + if (lpDDSAttachedSurface == nullptr && likely(SUCCEEDED(hrProxy))) + m_depthStencil = nullptr; + + return hrProxy; + } + + DDraw3Surface* ddraw3Surface = static_cast(lpDDSAttachedSurface); + + HRESULT hr = m_proxy->DeleteAttachedSurface(dwFlags, ddraw3Surface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + if (likely(m_depthStencil == ddraw3Surface)) { + ddraw3Surface->ClearParentSurface(); + m_depthStencil = nullptr; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) { + Logger::warn(">>> DDraw3Surface::EnumAttachedSurfaces"); + return m_parent->EnumAttachedSurfaces(lpContext, lpEnumSurfacesCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback) { + Logger::debug("<<< DDraw3Surface::EnumOverlayZOrders: Proxy"); + return m_proxy->EnumOverlayZOrders(dwFlags, lpContext, lpfnCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::Flip(LPDIRECTDRAWSURFACE3 lpDDSurfaceTargetOverride, DWORD dwFlags) { + // Lost surfaces are not flippable + HRESULT hr = m_proxy->IsLost(); + if (unlikely(FAILED(hr))) { + Logger::debug("DDraw3Surface::Flip: Lost surface"); + return hr; + } + + if (unlikely(!(m_commonSurf->IsFrontBuffer() || m_commonSurf->IsBackBufferOrFlippable()))) { + Logger::debug("DDraw3Surface::Flip: Unflippable surface"); + return DDERR_NOTFLIPPABLE; + } + + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Non-exclusive mode validations + if (unlikely(m_commonSurf->IsPrimarySurface() && !exclusiveMode)) { + Logger::debug("DDraw3Surface::Flip: Primary surface flip in non-exclusive mode"); + return DDERR_NOEXCLUSIVEMODE; + } + + // Exclusive mode validations + if (unlikely(m_commonSurf->IsBackBufferOrFlippable() && exclusiveMode)) { + Logger::debug("DDraw3Surface::Flip: Back buffer flip in exclusive mode"); + return DDERR_NOTFLIPPABLE; + } + + Com surf3 = static_cast(lpDDSurfaceTargetOverride); + if (lpDDSurfaceTargetOverride != nullptr) { + if (unlikely(!surf3->GetParent()->GetCommonSurface()->IsBackBufferOrFlippable())) { + Logger::debug("DDraw3Surface::Flip: Unflippable override surface"); + return DDERR_NOTFLIPPABLE; + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + Logger::debug("*** DDraw3Surface::Flip: Presenting"); + + m_commonIntf->ResetDrawTracking(); + + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(!IsInitialized())) + m_parent->InitializeD3D9(m_commonIntf->IsCurrentRenderTarget(m_parent)); + + BlitToDDrawSurface(m_proxy.ptr(), m_parent->GetD3D9()); + + if (likely(m_commonIntf->IsCurrentRenderTarget(m_parent))) { + if (lpDDSurfaceTargetOverride != nullptr) { + m_commonIntf->SetFlipRTSurfaceAndFlags(surf3->GetParent()->GetProxied(), dwFlags); + } else { + m_commonIntf->SetFlipRTSurfaceAndFlags(nullptr, dwFlags); + } + } + if (lpDDSurfaceTargetOverride != nullptr) { + return m_proxy->Flip(surf3->GetProxied(), dwFlags); + } else { + return m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } + } + + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + // If we don't have a valid D3D5 device, this means a D3D3 application + // is trying to flip the surface. Allow that for compatibility reasons. + } else { + Logger::debug("<<< DDraw3Surface::Flip: Proxy"); + if (lpDDSurfaceTargetOverride == nullptr) { + m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } else { + m_proxy->Flip(surf3->GetProxied(), dwFlags); + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetAttachedSurface(LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE3 *lplpDDAttachedSurface) { + Logger::debug("<<< DDraw3Surface::GetAttachedSurface: Proxy"); + + if (unlikely(lpDDSCaps == nullptr || lplpDDAttachedSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (lpDDSCaps->dwCaps & DDSCAPS_PRIMARYSURFACE) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for the primary surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FRONTBUFFER) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for the front buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_BACKBUFFER) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for the back buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FLIP) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for a flippable surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OFFSCREENPLAIN) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for an offscreen plain surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_ZBUFFER) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for a depth stencil"); + else if (lpDDSCaps->dwCaps & DDSCAPS_MIPMAP) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for a texture mip map"); + else if (lpDDSCaps->dwCaps & DDSCAPS_TEXTURE) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for a texture"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OVERLAY) + Logger::debug("DDraw3Surface::GetAttachedSurface: Querying for an overlay"); + + Com surface; + HRESULT hr = m_proxy->GetAttachedSurface(lpDDSCaps, &surface); + + // These are rather common, as some games query expecting to get nothing in return, for + // example it's a common use case to query the mip attach chain until nothing is returned + if (FAILED(hr)) { + Logger::debug("DDraw3Surface::GetAttachedSurface: Failed to find the requested surface"); + *lplpDDAttachedSurface = surface.ptr(); + return hr; + } + + try { + auto attachedSurfaceIter = m_attachedSurfaces.find(surface.ptr()); + if (unlikely(attachedSurfaceIter == m_attachedSurfaces.end())) { + // Return the already attached depth surface if it exists + if (unlikely(m_depthStencil != nullptr && surface.ptr() == m_depthStencil->GetProxied())) { + *lplpDDAttachedSurface = m_depthStencil.ref(); + } else { + Com ddraw3Surface = new DDraw3Surface(nullptr, std::move(surface), nullptr, this); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(ddraw3Surface->GetProxied()), + std::forward_as_tuple(ddraw3Surface.ref())); + *lplpDDAttachedSurface = ddraw3Surface.ref(); + } + } else { + *lplpDDAttachedSurface = attachedSurfaceIter->second.ref(); + } + } catch (const DxvkError& e) { + Logger::err(e.message()); + *lplpDDAttachedSurface = nullptr; + return DDERR_GENERIC; + } + + return DD_OK; + } + + // Blitting can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetBltStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw3Surface::GetBltStatus: Proxy"); + m_proxy->GetBltStatus(dwFlags); + } + + Logger::debug(">>> DDraw3Surface::GetBltStatus"); + + if (likely(dwFlags == DDGBS_CANBLT || dwFlags == DDGBS_ISBLTDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetCaps(LPDDSCAPS lpDDSCaps) { + Logger::debug(">>> DDraw3Surface::GetCaps"); + + if (unlikely(lpDDSCaps == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDSCaps = m_commonSurf->GetDesc()->ddsCaps; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper) { + Logger::debug(">>> DDraw3Surface::GetClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + DDrawClipper* clipper = m_commonSurf->GetClipper(); + + if (unlikely(clipper == nullptr)) + return DDERR_NOCLIPPERATTACHED; + + *lplpDDClipper = ref(clipper); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDraw3Surface::GetColorKey: Proxy"); + return m_proxy->GetColorKey(dwFlags, lpDDColorKey); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetDC(HDC *lphDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(lphDC == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lphDC); + + // Foward GetDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDraw3Surface::GetDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw3Surface::GetDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_parent->GetD3D9()->GetDC(lphDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDraw3Surface::GetDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDraw3Surface::GetDC: Proxy"); + return m_proxy->GetDC(lphDC); + } + + // Flipping can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetFlipStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw3Surface::GetFlipStatus: Proxy"); + m_proxy->GetFlipStatus(dwFlags); + } + + Logger::debug(">>> DDraw3Surface::GetFlipStatus"); + + if (likely(dwFlags == DDGFS_CANFLIP || dwFlags == DDGFS_ISFLIPDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetOverlayPosition(LPLONG lplX, LPLONG lplY) { + Logger::debug("<<< DDraw3Surface::GetOverlayPosition: Proxy"); + return m_proxy->GetOverlayPosition(lplX, lplY); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette) { + Logger::debug(">>> DDraw3Surface::GetPalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + DDrawPalette* palette = m_commonSurf->GetPalette(); + + if (unlikely(palette == nullptr)) + return DDERR_NOPALETTEATTACHED; + + *lplpDDPalette = ref(palette); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat) { + Logger::debug(">>> DDraw3Surface::GetPixelFormat"); + + if (unlikely(lpDDPixelFormat == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDPixelFormat = m_commonSurf->GetDesc()->ddpfPixelFormat; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc) { + Logger::debug(">>> DDraw3Surface::GetSurfaceDesc"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpDDSurfaceDesc->dwSize != sizeof(DDSURFACEDESC))) + return DDERR_INVALIDPARAMS; + + *lpDDSurfaceDesc = *m_commonSurf->GetDesc(); + + return DD_OK; + } + + // According to the docs: "Because the DirectDrawSurface object is initialized + // when it's created, this method always returns DDERR_ALREADYINITIALIZED." + HRESULT STDMETHODCALLTYPE DDraw3Surface::Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc) { + Logger::debug(">>> DDraw3Surface::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::IsLost() { + Logger::debug("<<< DDraw3Surface::IsLost: Proxy"); + return m_proxy->IsLost(); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::Lock(LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) { + Logger::debug("<<< DDraw3Surface::Lock: Proxy"); + + // It's highly unlikely anyone would do depth locks with IDirectDrawSurface3 + if (unlikely(m_commonSurf->IsDepthStencil())) { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw3Surface::Lock: Surface is a depth stencil"); + } + + return m_proxy->Lock(lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::ReleaseDC(HDC hDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + // Foward ReleaseDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDraw3Surface::ReleaseDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw3Surface::ReleaseDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_parent->GetD3D9()->ReleaseDC(hDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDraw3Surface::ReleaseDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDraw3Surface::ReleaseDC: Proxy"); + + HRESULT hr = m_proxy->ReleaseDC(hDC); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (m_commonSurf->IsTexture()) { + m_commonSurf->DirtyMipMaps(); + } else if (unlikely(m_commonIntf->GetOptions()->apitraceMode)) { + // We should ideally upload the surface contents here at all times, + // however some games are amazing, and do hundreds of locks on the same + // surface per frame, so this would absolutely tank performance + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw3Surface::ReleaseDC: Failed upload to d3d9 surface"); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::Restore() { + Logger::debug("<<< DDraw3Surface::Restore: Proxy"); + return m_proxy->Restore(); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper) { + Logger::debug("<<< DDraw3Surface::SetClipper: Proxy"); + + // A nullptr lpDDClipper gets the current clipper detached + if (lpDDClipper == nullptr) { + HRESULT hr = m_proxy->SetClipper(lpDDClipper); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(nullptr); + } else { + DDrawClipper* ddrawClipper = static_cast(lpDDClipper); + + HRESULT hr = m_proxy->SetClipper(ddrawClipper->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(ddrawClipper); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDraw3Surface::SetColorKey: Proxy"); + + HRESULT hr = m_proxy->SetColorKey(dwFlags, lpDDColorKey); + if (unlikely(FAILED(hr))) + return hr; + + hr = m_commonSurf->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::err("DDraw3Surface::SetColorKey: Failed to retrieve updated surface desc"); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::SetOverlayPosition(LONG lX, LONG lY) { + Logger::debug("<<< DDraw3Surface::SetOverlayPosition: Proxy"); + return m_proxy->SetOverlayPosition(lX, lY); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) { + Logger::debug("<<< DDraw3Surface::SetPalette: Proxy"); + + // A nullptr lpDDPalette gets the current palette detached + if (lpDDPalette == nullptr) { + HRESULT hr = m_proxy->SetPalette(lpDDPalette); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(nullptr); + } else { + DDrawPalette* ddrawPalette = static_cast(lpDDPalette); + + HRESULT hr = m_proxy->SetPalette(ddrawPalette->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(ddrawPalette); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::Unlock(LPVOID lpSurfaceData) { + Logger::debug("<<< DDraw3Surface::Unlock: Proxy"); + + HRESULT hr = m_proxy->Unlock(lpSurfaceData); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw3Surface::Unlock: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE3 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) { + Logger::debug("<<< DDraw3Surface::UpdateOverlay: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDDestSurface))) { + Logger::warn("DDraw3Surface::UpdateOverlay: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw3Surface* ddraw3Surface = static_cast(lpDDDestSurface); + return m_proxy->UpdateOverlay(lpSrcRect, ddraw3Surface->GetProxied(), lpDestRect, dwFlags, lpDDOverlayFx); + } + + // Docs: "This method is for software emulation only; it does nothing if the hardware supports overlays." + HRESULT STDMETHODCALLTYPE DDraw3Surface::UpdateOverlayDisplay(DWORD dwFlags) { + Logger::debug(">>> DDraw3Surface::UpdateOverlayDisplay"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE3 lpDDSReference) { + Logger::debug("<<< DDraw3Surface::UpdateOverlayZOrder: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSReference))) { + Logger::warn("DDraw3Surface::UpdateOverlayZOrder: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw3Surface* ddraw3Surface = static_cast(lpDDSReference); + return m_proxy->UpdateOverlayZOrder(dwFlags, ddraw3Surface->GetProxied()); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::GetDDInterface(LPVOID *lplpDD) { + if (unlikely(m_commonIntf->GetDDInterface() == nullptr)) { + Logger::warn("<<< DDraw2Surface::GetDDInterface: Proxy"); + return m_proxy->GetDDInterface(lplpDD); + } + + Logger::debug(">>> DDraw3Surface::GetDDInterface"); + + if (unlikely(lplpDD == nullptr)) + return DDERR_INVALIDPARAMS; + + // Was an easy footgun to return a proxied interface + *lplpDD = ref(m_commonIntf->GetDDInterface()); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::PageLock(DWORD dwFlags) { + Logger::debug("<<< DDraw3Surface::PageLock: Proxy"); + return m_proxy->PageLock(dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::PageUnlock(DWORD dwFlags) { + Logger::debug("<<< DDraw3Surface::PageUnlock: Proxy"); + return m_proxy->PageUnlock(dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDraw3Surface::SetSurfaceDesc(LPDDSURFACEDESC lpDDSD, DWORD dwFlags) { + Logger::debug("<<< DDraw3Surface::SetSurfaceDesc: Proxy"); + + // Can be used only to set the surface data and pixel format + // used by an explicit system-memory surface (will be validated) + HRESULT hr = m_proxy->SetSurfaceDesc(lpDDSD, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + hr = m_commonSurf->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::err("DDraw3Surface::SetSurfaceDesc: Failed to retrieve updated surface desc"); + + // We may need to recreate the d3d9 object based on the new desc + m_d3d9 = nullptr; + + if (!m_commonSurf->IsTexture()) { + InitializeOrUploadD3D9(); + } else { + m_commonSurf->DirtyMipMaps(); + } + + return hr; + } + +} diff --git a/src/ddraw/ddraw2/ddraw3_surface.h b/src/ddraw/ddraw2/ddraw3_surface.h new file mode 100644 index 00000000000..690324c63bb --- /dev/null +++ b/src/ddraw/ddraw2/ddraw3_surface.h @@ -0,0 +1,199 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../ddraw_common_surface.h" + +#include "../ddraw/ddraw_surface.h" + +#include + +namespace dxvk { + + class DDrawSurface; + class DDraw7Surface; + + /** + * \brief IDirectDrawSurface3 interface implementation + */ + class DDraw3Surface final : public DDrawWrappedObject { + + public: + + DDraw3Surface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDrawSurface* pParent, + DDraw3Surface* pParentSurf); + + ~DDraw3Surface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE AddAttachedSurface(LPDIRECTDRAWSURFACE3 lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE AddOverlayDirtyRect(LPRECT lpRect); + + HRESULT STDMETHODCALLTYPE Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE3 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx); + + HRESULT STDMETHODCALLTYPE BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE3 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans); + + HRESULT STDMETHODCALLTYPE DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE3 lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback); + + HRESULT STDMETHODCALLTYPE Flip(LPDIRECTDRAWSURFACE3 lpDDSurfaceTargetOverride, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetAttachedSurface(LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE3 *lplpDDAttachedSurface); + + HRESULT STDMETHODCALLTYPE GetBltStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDSCAPS lpDDSCaps); + + HRESULT STDMETHODCALLTYPE GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper); + + HRESULT STDMETHODCALLTYPE GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE GetDC(HDC *lphDC); + + HRESULT STDMETHODCALLTYPE GetFlipStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetOverlayPosition(LPLONG lplX, LPLONG lplY); + + HRESULT STDMETHODCALLTYPE GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette); + + HRESULT STDMETHODCALLTYPE GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat); + + HRESULT STDMETHODCALLTYPE GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE IsLost(); + + HRESULT STDMETHODCALLTYPE Lock(LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent); + + HRESULT STDMETHODCALLTYPE ReleaseDC(HDC hDC); + + HRESULT STDMETHODCALLTYPE Restore(); + + HRESULT STDMETHODCALLTYPE SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper); + + HRESULT STDMETHODCALLTYPE SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE SetOverlayPosition(LONG lX, LONG lY); + + HRESULT STDMETHODCALLTYPE SetPalette(LPDIRECTDRAWPALETTE lpDDPalette); + + HRESULT STDMETHODCALLTYPE Unlock(LPVOID lpSurfaceData); + + HRESULT STDMETHODCALLTYPE UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE3 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx); + + HRESULT STDMETHODCALLTYPE UpdateOverlayDisplay(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE3 lpDDSReference); + + HRESULT STDMETHODCALLTYPE GetDDInterface(LPVOID *lplpDD); + + HRESULT STDMETHODCALLTYPE PageLock(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE PageUnlock(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetSurfaceDesc(LPDDSURFACEDESC lpDDSD, DWORD dwFlags); + + DDrawCommonSurface* GetCommonSurface() const { + return m_commonSurf.ptr(); + } + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + d3d9::IDirect3DDevice9* GetD3D9Device() const { + return m_d3d9Device; + } + + DDraw3Surface* GetAttachedDepthStencil() { + // Fast path, since in most cases we already store the required surface + if (likely(m_depthStencil.ptr() != nullptr)) + return m_depthStencil.ptr(); + + DDSCAPS caps; + caps.dwCaps = DDSCAPS_ZBUFFER; + IDirectDrawSurface3* surface = nullptr; + HRESULT hr = GetAttachedSurface(&caps, &surface); + if (unlikely(FAILED(hr))) + return nullptr; + + m_depthStencil = reinterpret_cast(surface); + + return m_depthStencil.ptr(); + } + + void ClearAttachedDepthStencil() { + m_depthStencil = nullptr; + } + + void SetParentSurface(DDraw3Surface* surface) { + m_parentSurf = surface; + m_commonSurf->SetIsAttached(true); + } + + void ClearParentSurface() { + m_parentSurf = nullptr; + m_commonSurf->SetIsAttached(false); + } + + HRESULT InitializeOrUploadD3D9() { + return m_parent->InitializeOrUploadD3D9(); + } + + bool IsInitialized() { + return m_parent->IsInitialized(); + } + + private: + + inline void RefreshD3D9Device() { + if (likely(m_parent != nullptr)) { + d3d9::IDirect3DDevice9* d3d9Device = m_parent->GetCommonInterface()->GetD3D9Device(); + if (unlikely(m_d3d9Device != d3d9Device)) { + // Check if the device has been recreated and reset all D3D9 resources + if (m_d3d9Device != nullptr) { + Logger::debug("DDraw3Surface: Device context has changed, clearing all D3D9 resources"); + m_d3d9 = nullptr; + } + m_d3d9Device = d3d9Device; + } + } + } + + static uint32_t s_surfCount; + uint32_t m_surfCount = 0; + + Com m_commonSurf; + DDrawCommonInterface* m_commonIntf = nullptr; + + Com m_originSurf; + + DDraw3Surface* m_parentSurf = nullptr; + + d3d9::IDirect3DDevice9* m_d3d9Device = nullptr; + + // Back buffers will have depth stencil surfaces as attachments (in practice + // I have never seen more than one depth stencil being attached at a time) + Com m_depthStencil; + + // These are attached surfaces, which are typically mips or other types of generated + // surfaces, which need to exist for the entire lifecycle of their parent surface. + // They are implemented with linked list, so for example only one mip level + // will be held in a parent texture, and the next mip level will be held in the previous mip. + std::unordered_map> m_attachedSurfaces; + + }; + +} diff --git a/src/ddraw/ddraw4/ddraw4_interface.cpp b/src/ddraw/ddraw4/ddraw4_interface.cpp new file mode 100644 index 00000000000..6c778decc5f --- /dev/null +++ b/src/ddraw/ddraw4/ddraw4_interface.cpp @@ -0,0 +1,788 @@ +#include "ddraw4_interface.h" + +#include "ddraw4_surface.h" + +#include "../ddraw_clipper.h" +#include "../ddraw_palette.h" + +#include "../ddraw/ddraw_interface.h" +#include "../ddraw2/ddraw2_interface.h" +#include "../ddraw7/ddraw7_interface.h" + +#include "../d3d3/d3d3_interface.h" +#include "../d3d5/d3d5_interface.h" +#include "../d3d6/d3d6_interface.h" +#include + +namespace dxvk { + + uint32_t DDraw4Interface::s_intfCount = 0; + + DDraw4Interface::DDraw4Interface( + DDrawCommonInterface* commonIntf, + Com&& proxyIntf) + : DDrawWrappedObject(nullptr, std::move(proxyIntf), nullptr) + , m_commonIntf ( commonIntf ) { + // We need a temporary D3D9 interface at this point to retrieve the adapter identifier + Com d3d9Intf = d3d9::Direct3DCreate9(D3D_SDK_VERSION); + + d3d9::D3DADAPTER_IDENTIFIER9 adapterIdentifier9; + HRESULT hr = d3d9Intf->GetAdapterIdentifier(0, 0, &adapterIdentifier9); + if (unlikely(FAILED(hr))) { + throw DxvkError("DDraw4Interface: ERROR! Failed to get D3D9 adapter identifier!"); + } + + m_commonIntf->SetAdapterIdentifier(adapterIdentifier9); + + if (m_commonIntf->GetOrigin() == nullptr) + m_commonIntf->SetOrigin(this); + + m_commonIntf->SetDD4Interface(this); + + static bool s_apitraceModeWarningShown; + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && + !std::exchange(s_apitraceModeWarningShown, true))) + Logger::warn("DDraw4Interface: Apitrace mode is enabled. Performance will be suboptimal!"); + + m_intfCount = ++s_intfCount; + + Logger::debug(str::format("DDraw4Interface: Created a new interface nr. <<4-", m_intfCount, ">>")); + } + + DDraw4Interface::~DDraw4Interface() { + if (m_commonIntf->GetOrigin() == this) + m_commonIntf->SetOrigin(nullptr); + + m_commonIntf->SetDD4Interface(nullptr); + + Logger::debug(str::format("DDraw4Interface: Interface nr. <<4-", m_intfCount, ">> bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDraw4Interface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + // Standard way of retrieving a D3D6 interface + if (riid == __uuidof(IDirect3D3)) { + Logger::debug("DDraw4Interface::QueryInterface: Query for IDirect3D3"); + + // Initialize the IDirect3D3 interlocked object + if (unlikely(m_d3d6Intf == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + m_d3d6Intf = new D3D6Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + } + + *ppvObject = m_d3d6Intf.ref(); + + return S_OK; + } + // Some games query for legacy ddraw interfaces + if (unlikely(riid == __uuidof(IDirectDraw))) { + if (m_commonIntf->GetDDInterface() != nullptr) { + Logger::debug("DDraw4Interface::QueryInterface: Query for existing IDirectDraw"); + return m_commonIntf->GetDDInterface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Interface::QueryInterface: Query for IDirectDraw"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDrawInterface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Some games query for legacy ddraw interfaces + if (unlikely(riid == __uuidof(IDirectDraw2))) { + if (m_commonIntf->GetDD2Interface() != nullptr) { + Logger::debug("DDraw4Interface::QueryInterface: Query for existing IDirectDraw2"); + return m_commonIntf->GetDD2Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Interface::QueryInterface: Query for IDirectDraw2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw2Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Legacy way of getting a DDraw7 interface + if (unlikely(riid == __uuidof(IDirectDraw7))) { + if (m_commonIntf->GetDD7Interface() != nullptr) { + Logger::debug("DDraw4Interface::QueryInterface: Query for existing IDirectDraw7"); + return m_commonIntf->GetDD7Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Interface::QueryInterface: Query for IDirectDraw7"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw7Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Standard way of retrieving a D3D3 interface + if (unlikely(riid == __uuidof(IDirect3D))) { + if (m_commonIntf->GetDDInterface() != nullptr) { + Logger::debug("DDraw4Interface::QueryInterface: Forwarded query for IDirect3D"); + return m_commonIntf->GetDDInterface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Interface::QueryInterface: Query for IDirect3D"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + Com d3d3Intf = new D3D3Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + m_commonIntf->SetD3D3Interface(d3d3Intf.ptr()); + *ppvObject = d3d3Intf.ref(); + + return S_OK; + } + // Standard way of retrieving a D3D5 interface + if (unlikely(riid == __uuidof(IDirect3D2))) { + if (m_commonIntf->GetDDInterface() != nullptr) { + Logger::debug("DDraw4Interface::QueryInterface: Forwarded query for IDirect3D2"); + return m_commonIntf->GetDDInterface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Interface::QueryInterface: Query for IDirect3D2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + Com d3d5Intf = new D3D5Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + *ppvObject = d3d5Intf.ref(); + + return S_OK; + } + // Quite a lot of games query for this IID during intro playback + if (unlikely(riid == GUID_IAMMediaStream)) { + Logger::debug("DDraw4Interface::QueryInterface: Query for IAMMediaStream"); + return m_proxy->QueryInterface(riid, ppvObject); + } + // Also seen queried by some games, such as V-Rally 2: Expert Edition + if (unlikely(riid == GUID_IMediaStream)) { + Logger::debug("DDraw4Interface::QueryInterface: Query for IMediaStream"); + return m_proxy->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // The documentation states: "The IDirectDraw4::Compact method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw4Interface::Compact() { + Logger::debug(">>> DDraw4Interface::Compact"); + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::CreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw4Interface::CreateClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + Com lplpDDClipperProxy; + HRESULT hr = m_proxy->CreateClipper(dwFlags, &lplpDDClipperProxy, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + *lplpDDClipper = ref(new DDrawClipper(std::move(lplpDDClipperProxy), this)); + } else { + Logger::warn("DDraw4Interface::CreateClipper: Failed to create proxy clipper"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::CreatePalette(DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw4Interface::CreatePalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + Com lplpDDPaletteProxy; + HRESULT hr = m_proxy->CreatePalette(dwFlags, lpColorTable, &lplpDDPaletteProxy, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + *lplpDDPalette = ref(new DDrawPalette(std::move(lplpDDPaletteProxy), this)); + } else { + Logger::warn("DDraw4Interface::CreatePalette: Failed to create proxy palette"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::CreateSurface(LPDDSURFACEDESC2 lpDDSurfaceDesc, LPDIRECTDRAWSURFACE4 *lplpDDSurface, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw4Interface::CreateSurface"); + + // The cooperative level is always checked first + if (unlikely(!m_commonIntf->IsCooperativeLevelSet())) + return DDERR_NOCOOPERATIVELEVELSET; + + if (unlikely(lpDDSurfaceDesc == nullptr || lplpDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDSurface); + + // Because we are removing the DDSCAPS_WRITEONLY flag below, we need + // to first validate the combinations that would otherwise cause issues + HRESULT hr = ValidateSurfaceFlags(lpDDSurfaceDesc); + if (unlikely(FAILED(hr))) + return hr; + + // We need to ensure we can always read from surfaces for upload to + // D3D9, so always strip the DDSCAPS_WRITEONLY flag on creation + lpDDSurfaceDesc->ddsCaps.dwCaps &= ~DDSCAPS_WRITEONLY; + // Similarly strip the DDSCAPS2_OPAQUE flag on texture creation + if (lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_TEXTURE) { + lpDDSurfaceDesc->ddsCaps.dwCaps2 &= ~DDSCAPS2_OPAQUE; + } + + if (unlikely((lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_ZBUFFER) + && (lpDDSurfaceDesc->ddpfPixelFormat.dwZBitMask == 0xFFFFFFFF))) { + if (m_commonIntf->GetOptions()->useD24X8forD32) { + // In case of up-front unsupported and unadvertised D32 depth stencil use, + // replace it with D24X8, as some games, such as Sacrifice, rely on it + // to properly enable 32-bit display modes (and revert to 16-bit otherwise) + Logger::info("DDraw4Interface::CreateSurface: Using D24X8 instead of D32"); + lpDDSurfaceDesc->ddpfPixelFormat.dwZBitMask = 0xFFFFFF; + } else { + Logger::warn("DDraw4Interface::CreateSurface: Use of unsupported D32"); + } + } + + Com ddrawSurface4Proxied; + hr = m_proxy->CreateSurface(lpDDSurfaceDesc, &ddrawSurface4Proxied, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + try{ + Com surface4 = new DDraw4Surface(nullptr, std::move(ddrawSurface4Proxied), this, nullptr, true); + if (unlikely(lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)) + m_commonIntf->SetPrimarySurface(surface4->GetCommonSurface()); + *lplpDDSurface = surface4.ref(); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + // Some games simply try creating surfaces with various formats until something works... + } else { + Logger::debug("DDraw4Interface::CreateSurface: Failed to create proxy surface"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::DuplicateSurface(LPDIRECTDRAWSURFACE4 lpDDSurface, LPDIRECTDRAWSURFACE4 *lplpDupDDSurface) { + Logger::debug("<<< DDraw4Interface::DuplicateSurface: Proxy"); + + if (m_commonIntf->IsWrappedSurface(lpDDSurface)) { + InitReturnPtr(lplpDupDDSurface); + + DDraw4Surface* ddraw4Surface = static_cast(lpDDSurface); + Com dupSurface4; + HRESULT hr = m_proxy->DuplicateSurface(ddraw4Surface->GetProxied(), &dupSurface4); + if (likely(SUCCEEDED(hr))) { + try { + *lplpDupDDSurface = ref(new DDraw4Surface(nullptr, std::move(dupSurface4), this, nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } + return hr; + } else { + if (unlikely(lpDDSurface != nullptr)) { + Logger::warn("DDraw7Interface::DuplicateSurface: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + return m_proxy->DuplicateSurface(lpDDSurface, lplpDupDDSurface); + } + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::EnumDisplayModes(DWORD dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK2 lpEnumModesCallback) { + Logger::debug("<<< DDraw4Interface::EnumDisplayModes: Proxy"); + return m_proxy->EnumDisplayModes(dwFlags, lpDDSurfaceDesc, lpContext, lpEnumModesCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::EnumSurfaces(DWORD dwFlags, LPDDSURFACEDESC2 lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpEnumSurfacesCallback) { + Logger::debug(">>> DDraw4Interface::EnumSurfaces: Proxy"); + + if (unlikely(lpEnumSurfacesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + std::vector attachedSurfaces; + // Enumerate all surfaces from the underlying DDraw implementation + m_proxy->EnumSurfaces(dwFlags, lpDDSD, reinterpret_cast(&attachedSurfaces), EnumAttachedSurfaces4Callback); + + HRESULT hr = DDENUMRET_OK; + + // Wrap surfaces as needed and perform the actual callback the application is requesting + auto surfaceIt = attachedSurfaces.begin(); + while (surfaceIt != attachedSurfaces.end() && hr == DDENUMRET_OK) { + Com surface4 = surfaceIt->surface4; + + Com ddraw4Surface = new DDraw4Surface(nullptr, std::move(surface4), this, nullptr, false); + hr = lpEnumSurfacesCallback(ddraw4Surface.ref(), &surfaceIt->desc2, lpContext); + + ++surfaceIt; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::FlipToGDISurface() { + Logger::debug("*** DDraw4Interface::FlipToGDISurface: Ignoring"); + + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) + return m_proxy->FlipToGDISurface(); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) { + Logger::debug("<<< DDraw4Interface::GetCaps: Proxy"); + + HRESULT hr = m_proxy->GetCaps(lpDDDriverCaps, lpDDHELCaps); + if (unlikely(FAILED(hr))) + return hr; + + static constexpr DWORD Megabytes = 1024 * 1024; + static constexpr DWORD MaxMemory = ddrawCaps::MaxTextureMemory * Megabytes; + static constexpr DWORD ReservedMemory = ddrawCaps::ReservedTextureMemory * Megabytes; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + // Properly fill in the dwVidMemTotal / dwVidMemFree fields + DWORD total9 = 0; + DWORD free9 = 0; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (likely(d3d9Device != nullptr)) { + Logger::debug("DDraw4Interface::GetCaps: Getting memory stats from D3D9"); + + total9 = static_cast(m_commonIntf->GetTotalTextureMemory()); + free9 = static_cast(d3d9Device->GetAvailableTextureMem()); + + if (likely(total9 >= MaxMemory)) { + const DWORD delta = total9 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free9 > delta + ReservedMemory ? free9 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw4Interface::GetCaps: Total: ", total9)); + Logger::debug(str::format("DDraw4Interface::GetCaps: Free : ", free9)); + } else { + Logger::debug("DDraw4Interface::GetCaps: Getting memory stats from DDraw"); + + const DWORD total6 = lpDDDriverCaps != nullptr ? lpDDDriverCaps->dwVidMemTotal : 0; + const DWORD free6 = lpDDDriverCaps != nullptr ? lpDDDriverCaps->dwVidMemFree : 0; + + Logger::debug(str::format("DDraw4Interface::GetCaps: DDraw Total: ", total6)); + Logger::debug(str::format("DDraw4Interface::GetCaps: DDraw Free : ", free6)); + + if (unlikely(total6 < MaxMemory)) { + total9 = total6; + free9 = free6; + } else { + const DWORD delta = total6 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free6 > delta + ReservedMemory ? free6 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw4Interface::GetCaps: Total: ", total9)); + Logger::debug(str::format("DDraw4Interface::GetCaps: Free : ", free9)); + } + + // Report all possible flip capabilities as supported + if (lpDDDriverCaps != nullptr) { + lpDDDriverCaps->dwCaps2 |= DDCAPS2_FLIPINTERVAL | DDCAPS2_FLIPNOVSYNC; + lpDDDriverCaps->dwZBufferBitDepths = d3dOptions->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + lpDDDriverCaps->dwVidMemTotal = total9; + lpDDDriverCaps->dwVidMemFree = free9; + lpDDDriverCaps->dwNumFourCCCodes = ddrawCaps::NumberOfFOURCCCodes; + } + if (lpDDHELCaps != nullptr) { + lpDDHELCaps->dwCaps2 |= DDCAPS2_FLIPINTERVAL | DDCAPS2_FLIPNOVSYNC; + lpDDHELCaps->dwZBufferBitDepths = d3dOptions->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + lpDDHELCaps->dwVidMemTotal = total9; + lpDDHELCaps->dwVidMemFree = free9; + lpDDHELCaps->dwNumFourCCCodes = ddrawCaps::NumberOfFOURCCCodes; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetDisplayMode(LPDDSURFACEDESC2 lpDDSurfaceDesc) { + Logger::debug("<<< DDraw4Interface::GetDisplayMode: Proxy"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + HRESULT hr = m_proxy->GetDisplayMode(lpDDSurfaceDesc); + if (unlikely(FAILED(hr))) + return hr; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->mask8BitModes && lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount == 8)) + lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount = 16; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetFourCCCodes(LPDWORD lpNumCodes, LPDWORD lpCodes) { + Logger::debug(">>> DDraw4Interface::GetFourCCCodes"); + + if (likely(lpNumCodes != nullptr && lpCodes != nullptr)) { + const uint32_t copyNumCodes = std::min(ddrawCaps::NumberOfFOURCCCodes, *lpNumCodes); + for (uint32_t i = 0; i < copyNumCodes; i++) { + lpCodes[i] = ddrawCaps::SupportedFourCCs[i]; + } + } + + if (lpNumCodes != nullptr) + *lpNumCodes = ddrawCaps::NumberOfFOURCCCodes; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetGDISurface(LPDIRECTDRAWSURFACE4 *lplpGDIDDSurface) { + Logger::debug("<<< DDraw4Interface::GetGDISurface: Proxy"); + + if(unlikely(lplpGDIDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpGDIDDSurface); + + Com gdiSurface; + HRESULT hr = m_proxy->GetGDISurface(&gdiSurface); + + if (unlikely(FAILED(hr))) { + Logger::debug("DDraw4Interface::GetGDISurface: Failed to retrieve GDI surface"); + return hr; + } + + if (unlikely(m_commonIntf->IsWrappedSurface(gdiSurface.ptr()))) { + *lplpGDIDDSurface = gdiSurface.ref(); + } else { + Logger::debug("DDraw4Interface::GetGDISurface: Received a non-wrapped GDI surface"); + try { + *lplpGDIDDSurface = ref(new DDraw4Surface(nullptr, std::move(gdiSurface), this, nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetMonitorFrequency(LPDWORD lpdwFrequency) { + Logger::debug("<<< DDraw4Interface::GetMonitorFrequency: Proxy"); + return m_proxy->GetMonitorFrequency(lpdwFrequency); + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetScanLine(LPDWORD lpdwScanLine) { + Logger::debug("<<< DDraw4Interface::GetScanLine: Proxy"); + return m_proxy->GetScanLine(lpdwScanLine); + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetVerticalBlankStatus(LPBOOL lpbIsInVB) { + Logger::debug("<<< DDraw4Interface::GetVerticalBlankStatus: Proxy"); + return m_proxy->GetVerticalBlankStatus(lpbIsInVB); + } + + // Should technically always return DDERR_ALREADYINITIALIZED, unless the + // interface is created via IClassFactory, however Requiem: Avenging Angel + // expects it to work on a regular interface too, after initially creating + // and releasing an interface through IClassFactory (but never initializing it). + // On native DDraw the initial interface most likely gets reused. In practice, + // applications that don't use IClassFactory won't call this, so keep it simple. + HRESULT STDMETHODCALLTYPE DDraw4Interface::Initialize(GUID* lpGUID) { + Logger::debug(">>> DDraw4Interface::Initialize"); + + if (unlikely(m_commonIntf->IsInitialized())) + return DDERR_ALREADYINITIALIZED; + + m_commonIntf->MarkAsInitialized(); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::RestoreDisplayMode() { + Logger::debug("<<< DDraw4Interface::RestoreDisplayMode: Proxy"); + return m_proxy->RestoreDisplayMode(); + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::SetCooperativeLevel(HWND hWnd, DWORD dwFlags) { + Logger::debug("<<< DDraw4Interface::SetCooperativeLevel: Proxy"); + + HRESULT hr = m_proxy->SetCooperativeLevel(hWnd, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + m_commonIntf->SetCooperativeLevel(hWnd, dwFlags); + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags) { + Logger::debug("<<< DDraw4Interface::SetDisplayMode: Proxy"); + + Logger::debug(str::format("DDraw4Interface::SetDisplayMode: ", dwWidth, "x", dwHeight, ":", dwBPP, "@", dwRefreshRate)); + + HRESULT hr = m_proxy->SetDisplayMode(dwWidth, dwHeight, dwBPP, dwRefreshRate, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + DDrawCommonSurface* ps = m_commonIntf->GetPrimarySurface(); + + if (likely(ps != nullptr)) { + hr = ps->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::warn("DDraw4Interface::SetDisplayMode: Failed to update primary surface desc"); + } + + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent && + m_commonIntf->GetOptions()->backBufferResize)) { + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Ignore any mode size dimensions when in windowed present mode + if (exclusiveMode) { + Logger::debug("DDraw4Interface::SetDisplayMode: Exclusive full-screen present mode in use"); + DDrawModeSize* modeSize = m_commonIntf->GetModeSize(); + if (modeSize->width != dwWidth || modeSize->height != dwHeight) { + modeSize->width = dwWidth; + modeSize->height = dwHeight; + } + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::WaitForVerticalBlank(DWORD dwFlags, HANDLE hEvent) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw4Interface::WaitForVerticalBlank: Proxy"); + m_proxy->WaitForVerticalBlank(dwFlags, hEvent); + } + + Logger::debug(">>> DDraw4Interface::WaitForVerticalBlank"); + + if (unlikely(dwFlags & DDWAITVB_BLOCKBEGINEVENT)) + return DDERR_UNSUPPORTED; + + // Switch to a default presentation interval when an application + // tries to wait for vertical blank, if we're not already doing so + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (unlikely(d3d9Device != nullptr && !m_commonIntf->GetWaitForVBlank())) { + Logger::info("DDraw4Interface::WaitForVerticalBlank: Switching to D3DPRESENT_INTERVAL_DEFAULT for presentation"); + + d3d9::D3DPRESENT_PARAMETERS resetParams = m_commonIntf->GetPresentParameters(); + resetParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + HRESULT hrReset = m_commonIntf->ResetD3D9Swapchain(&resetParams); + if (likely(SUCCEEDED(hrReset))) + m_commonIntf->SetWaitForVBlank(true); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetAvailableVidMem(LPDDSCAPS2 lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree) { + Logger::debug(">>> DDraw4Interface::GetAvailableVidMem"); + + if (unlikely(lpdwTotal == nullptr && lpdwFree == nullptr)) + return DD_OK; + + static constexpr DWORD Megabytes = 1024 * 1024; + static constexpr DWORD MaxMemory = ddrawCaps::MaxTextureMemory * Megabytes; + static constexpr DWORD ReservedMemory = ddrawCaps::ReservedTextureMemory * Megabytes; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (likely(d3d9Device != nullptr)) { + Logger::debug("DDraw4Interface::GetAvailableVidMem: Getting memory stats from D3D9"); + + DWORD total9 = static_cast(m_commonIntf->GetTotalTextureMemory()); + DWORD free9 = static_cast(d3d9Device->GetAvailableTextureMem()); + + if (likely(total9 >= MaxMemory)) { + const DWORD delta = total9 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free9 > delta + ReservedMemory ? free9 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw4Interface::GetAvailableVidMem: Total: ", total9)); + Logger::debug(str::format("DDraw4Interface::GetAvailableVidMem: Free : ", free9)); + + if (lpdwTotal != nullptr) + *lpdwTotal = total9; + if (lpdwFree != nullptr) + *lpdwFree = free9; + + } else { + Logger::debug("DDraw4Interface::GetAvailableVidMem: Getting memory stats from DDraw"); + + DWORD total6 = 0; + DWORD free6 = 0; + + HRESULT hr = m_proxy->GetAvailableVidMem(lpDDCaps, &total6, &free6); + if (unlikely(FAILED(hr))) { + Logger::err("DDraw4Interface::GetAvailableVidMem: Failed proxied call"); + if (lpdwTotal != nullptr) + *lpdwTotal = 0; + if (lpdwFree != nullptr) + *lpdwFree = 0; + return hr; + } + + Logger::debug(str::format("DDraw4Interface::GetAvailableVidMem: DDraw Total: ", total6)); + Logger::debug(str::format("DDraw4Interface::GetAvailableVidMem: DDraw Free : ", free6)); + + DWORD total9 = 0; + DWORD free9 = 0; + + if (unlikely(total6 < MaxMemory)) { + total9 = total6; + free9 = free6; + } else { + const DWORD delta = total6 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free6 > delta + ReservedMemory ? free6 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw4Interface::GetAvailableVidMem: Total: ", total9)); + Logger::debug(str::format("DDraw4Interface::GetAvailableVidMem: Free : ", free9)); + + if (lpdwTotal != nullptr) + *lpdwTotal = total9; + if (lpdwFree != nullptr) + *lpdwFree = free9; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetSurfaceFromDC(HDC hdc, LPDIRECTDRAWSURFACE4 *pSurf) { + if (unlikely(pSurf == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(pSurf); + + Com surface; + HRESULT hr = m_proxy->GetSurfaceFromDC(hdc, &surface); + + if (unlikely(FAILED(hr))) { + Logger::warn("DDraw4Interface::GetSurfaceFromDC: Failed to get surface from DC"); + return hr; + } + + try { + *pSurf = ref(new DDraw4Surface(nullptr, std::move(surface), this, nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::RestoreAllSurfaces() { + Logger::debug("<<< DDraw4Interface::RestoreAllSurfaces: Proxy"); + return m_proxy->RestoreAllSurfaces(); + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::TestCooperativeLevel() { + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + + if (unlikely(d3d9Device == nullptr)) { + Logger::debug("<<< DDraw4Interface::TestCooperativeLevel: Proxy"); + return m_proxy->TestCooperativeLevel(); + } + + Logger::debug(">>> DDraw4Interface::TestCooperativeLevel"); + + HRESULT hr = d3d9Device->TestCooperativeLevel(); + if (unlikely(FAILED(hr))) + return DDERR_NOEXCLUSIVEMODE; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Interface::GetDeviceIdentifier(LPDDDEVICEIDENTIFIER pDDDI, DWORD dwFlags) { + Logger::debug(">>> DDraw4Interface::GetDeviceIdentifier"); + + if (unlikely(pDDDI == nullptr)) + return DDERR_INVALIDPARAMS; + + // We need to share the device identifier with the underlying + // DDraw implementation, as some games validate it in various places + DDDEVICEIDENTIFIER pDDDIproxy = { }; + HRESULT hr = m_proxy->GetDeviceIdentifier(&pDDDIproxy, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + const d3d9::D3DADAPTER_IDENTIFIER9* adapterIdentifier9 = m_commonIntf->GetAdapterIdentifier(); + // This is typically the "2D accelerator" in the system + if (unlikely(dwFlags & DDGDI_GETHOSTIDENTIFIER)) { + Logger::debug("DDraw4Interface::GetDeviceIdentifier: Retrieving secondary adapter info"); + CopyToStringArray(pDDDI->szDriver, "vga.dll"); + CopyToStringArray(pDDDI->szDescription, "Standard VGA Adapter"); + pDDDI->liDriverVersion.QuadPart = 0; + pDDDI->dwVendorId = 0; + pDDDI->dwDeviceId = 0; + pDDDI->dwSubSysId = 0; + pDDDI->dwRevision = 0; + pDDDI->guidDeviceIdentifier = pDDDIproxy.guidDeviceIdentifier; + } else { + Logger::debug("DDraw4Interface::GetDeviceIdentifier: Retrieving primary adapter info"); + memcpy(&pDDDI->szDriver, &adapterIdentifier9->Driver, sizeof(adapterIdentifier9->Driver)); + memcpy(&pDDDI->szDescription, &adapterIdentifier9->Description, sizeof(adapterIdentifier9->Description)); + pDDDI->liDriverVersion.QuadPart = adapterIdentifier9->DriverVersion.QuadPart; + pDDDI->dwVendorId = adapterIdentifier9->VendorId; + pDDDI->dwDeviceId = adapterIdentifier9->DeviceId; + pDDDI->dwSubSysId = adapterIdentifier9->SubSysId; + pDDDI->dwRevision = adapterIdentifier9->Revision; + pDDDI->guidDeviceIdentifier = pDDDIproxy.guidDeviceIdentifier; + } + + Logger::info(str::format("DDraw4Interface::GetDeviceIdentifier: Reporting: ", pDDDI->szDescription)); + + return DD_OK; + } + +} diff --git a/src/ddraw/ddraw4/ddraw4_interface.h b/src/ddraw/ddraw4/ddraw4_interface.h new file mode 100644 index 00000000000..178d43e4e62 --- /dev/null +++ b/src/ddraw/ddraw4/ddraw4_interface.h @@ -0,0 +1,96 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_caps.h" +#include "../ddraw_format.h" + +#include "../ddraw_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +namespace dxvk { + + class D3D6Interface; + class DDraw4Surface; + + /** + * \brief DirectDraw4 interface implementation + */ + class DDraw4Interface final : public DDrawWrappedObject { + + public: + DDraw4Interface( + DDrawCommonInterface* commonIntf, + Com&& proxyIntf); + + ~DDraw4Interface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE Compact(); + + HRESULT STDMETHODCALLTYPE CreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreatePalette(DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateSurface(LPDDSURFACEDESC2 lpDDSurfaceDesc, LPDIRECTDRAWSURFACE4 *lplpDDSurface, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE DuplicateSurface(LPDIRECTDRAWSURFACE4 lpDDSurface, LPDIRECTDRAWSURFACE4 *lplpDupDDSurface); + + HRESULT STDMETHODCALLTYPE EnumDisplayModes(DWORD dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK2 lpEnumModesCallback); + + HRESULT STDMETHODCALLTYPE EnumSurfaces(DWORD dwFlags, LPDDSURFACEDESC2 lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE FlipToGDISurface(); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps); + + HRESULT STDMETHODCALLTYPE GetDisplayMode(LPDDSURFACEDESC2 lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE GetFourCCCodes(LPDWORD lpNumCodes, LPDWORD lpCodes); + + HRESULT STDMETHODCALLTYPE GetGDISurface(LPDIRECTDRAWSURFACE4 *lplpGDIDDSurface); + + HRESULT STDMETHODCALLTYPE GetMonitorFrequency(LPDWORD lpdwFrequency); + + HRESULT STDMETHODCALLTYPE GetScanLine(LPDWORD lpdwScanLine); + + HRESULT STDMETHODCALLTYPE GetVerticalBlankStatus(LPBOOL lpbIsInVB); + + HRESULT STDMETHODCALLTYPE Initialize(GUID* lpGUID); + + HRESULT STDMETHODCALLTYPE RestoreDisplayMode(); + + HRESULT STDMETHODCALLTYPE SetCooperativeLevel(HWND hWnd, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE WaitForVerticalBlank(DWORD dwFlags, HANDLE hEvent); + + HRESULT STDMETHODCALLTYPE GetAvailableVidMem(LPDDSCAPS2 lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree); + + HRESULT STDMETHODCALLTYPE GetSurfaceFromDC(HDC hdc, LPDIRECTDRAWSURFACE4 *pSurf); + + HRESULT STDMETHODCALLTYPE RestoreAllSurfaces(); + + HRESULT STDMETHODCALLTYPE TestCooperativeLevel(); + + HRESULT STDMETHODCALLTYPE GetDeviceIdentifier(LPDDDEVICEIDENTIFIER pDDDI, DWORD dwFlags); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf.ptr(); + } + + private: + + static uint32_t s_intfCount; + uint32_t m_intfCount = 0; + + Com m_commonIntf; + + Com m_d3d6Intf; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw4/ddraw4_surface.cpp b/src/ddraw/ddraw4/ddraw4_surface.cpp new file mode 100644 index 00000000000..4c11d05ff38 --- /dev/null +++ b/src/ddraw/ddraw4/ddraw4_surface.cpp @@ -0,0 +1,1537 @@ +#include "ddraw4_surface.h" + +#include "ddraw4_interface.h" + +#include "../ddraw_gamma.h" +#include "../ddraw/ddraw_surface.h" +#include "../ddraw2/ddraw2_surface.h" +#include "../ddraw2/ddraw3_surface.h" +#include "../ddraw7/ddraw7_surface.h" +#include + +#include "../d3d3/d3d3_texture.h" +#include "../d3d6/d3d6_texture.h" + +namespace dxvk { + + uint32_t DDraw4Surface::s_surfCount = 0; + + DDraw4Surface::DDraw4Surface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDraw4Interface* pParent, + DDraw4Surface* pParentSurf, + bool isChildObject) + : DDrawWrappedObject(pParent, std::move(surfProxy), nullptr) + , m_isChildObject ( isChildObject ) + , m_commonSurf ( commonSurf ) + , m_parentSurf ( pParentSurf ) { + if (m_parent != nullptr) { + m_commonIntf = m_parent->GetCommonInterface(); + } else if (m_parentSurf != nullptr) { + m_commonIntf = m_parentSurf->GetCommonInterface(); + } else if (m_commonSurf != nullptr) { + m_commonIntf = m_commonSurf->GetCommonInterface(); + } else { + throw DxvkError("DDraw4Surface: ERROR! Failed to retrieve the common interface!"); + } + + if (m_commonSurf == nullptr) + m_commonSurf = new DDrawCommonSurface(m_commonIntf); + + // Retrieve and cache the proxy surface desc + if (!m_commonSurf->IsDesc2Set()) { + DDSURFACEDESC2 desc2; + desc2.dwSize = sizeof(DDSURFACEDESC2); + HRESULT hr = m_proxy->GetSurfaceDesc(&desc2); + + if (unlikely(FAILED(hr))) { + throw DxvkError("DDraw4Surface: ERROR! Failed to retrieve new surface desc!"); + } else { + m_commonSurf->SetDesc2(desc2); + } + } + + m_commonIntf->AddWrappedSurface(this); + + m_commonSurf->SetDD4Surface(this); + + if (m_parentSurf != nullptr && !m_commonSurf->IsFrontBuffer() + && m_parentSurf->GetCommonSurface()->IsBackBufferOrFlippable() + && !m_commonIntf->GetOptions()->forceSingleBackBuffer) { + const uint32_t index = m_parentSurf->GetCommonSurface()->GetBackBufferIndex(); + m_commonSurf->IncrementBackBufferIndex(index); + } + + if (m_parent != nullptr && m_isChildObject) + m_parent->AddRef(); + + m_surfCount = ++s_surfCount; + + Logger::debug(str::format("DDraw4Surface: Created a new surface nr. [[4-", m_surfCount, "]]")); + + if (m_commonSurf->GetOrigin() == nullptr) { + m_commonSurf->SetOrigin(this); + m_commonSurf->SetIsAttached(m_parentSurf != nullptr); + m_commonSurf->ListSurfaceDetails(); + } + } + + DDraw4Surface::~DDraw4Surface() { + if (m_commonSurf->GetOrigin() == this) + m_commonSurf->SetOrigin(nullptr); + + // Clear the cached depth stencil on the parent if matched + if (unlikely(m_parentSurf != nullptr && m_commonSurf->IsDepthStencil() + && m_parentSurf->GetAttachedDepthStencil() == this)) { + m_parentSurf->ClearAttachedDepthStencil(); + } + + m_commonIntf->RemoveWrappedSurface(this); + + // Release all public references on all attached surfaces + for (auto & attachedSurface : m_attachedSurfaces) { + uint32_t attachedRef; + do { + attachedRef = attachedSurface.second->Release(); + } while (attachedRef > 0); + } + + if (m_parent != nullptr && m_isChildObject) + m_parent->Release(); + + m_commonSurf->SetDD4Surface(nullptr); + + Logger::debug(str::format("DDraw4Surface: Surface nr. [[4-", m_surfCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDraw4Surface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + // Standard way of retrieving a texture for d3d6 SetTexture calls + if (unlikely(riid == __uuidof(IDirect3DTexture2))) { + Logger::debug("DDraw4Surface::QueryInterface: Query for IDirect3DTexture2"); + + if (unlikely(m_texture6 == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + D3DTEXTUREHANDLE nextHandle = m_commonIntf->GetNextTextureHandle(); + m_texture6 = new D3D6Texture(std::move(ppvProxyObject), this, nextHandle); + // D3D6Textures don't need handle lookups so skip storing them + } + + *ppvObject = m_texture6.ref(); + + return S_OK; + } + // Shouldn't ever be called in practice + if (unlikely(riid == __uuidof(IDirect3DTexture))) { + Logger::warn("DDraw4Surface::QueryInterface: Query for IDirect3DTexture"); + return E_NOINTERFACE; + } + // Wrap IDirectDrawGammaControl, to potentially ignore application set gamma ramps + if (riid == __uuidof(IDirectDrawGammaControl)) { + Logger::debug("DDraw4Surface::QueryInterface: Query for IDirectDrawGammaControl"); + void* gammaControlProxiedVoid = nullptr; + // This can never reasonably fail + m_proxy->QueryInterface(__uuidof(IDirectDrawGammaControl), &gammaControlProxiedVoid); + Com gammaControlProxied = static_cast(gammaControlProxiedVoid); + *ppvObject = ref(new DDrawGammaControl(m_commonSurf.ptr(), std::move(gammaControlProxied), this)); + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawColorControl))) { + Logger::debug("DDraw4Surface::QueryInterface: Query for IDirectDrawColorControl"); + return E_NOINTERFACE; + } + if (unlikely(riid == __uuidof(IUnknown) + || riid == __uuidof(IDirectDrawSurface))) { + if (m_commonSurf->GetDDSurface() != nullptr) { + Logger::debug("DDraw4Surface::QueryInterface: Query for existing IDirectDrawSurface"); + return m_commonSurf->GetDDSurface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Surface::QueryInterface: Query for IDirectDrawSurface"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDrawSurface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDDInterface(), nullptr, false)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface2))) { + if (m_commonSurf->GetDD2Surface() != nullptr) { + Logger::debug("DDraw4Surface::QueryInterface: Query for existing IDirectDrawSurface2"); + return m_commonSurf->GetDD2Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Surface::QueryInterface: Query for IDirectDrawSurface2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw2Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonSurf->GetDDSurface(), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface3))) { + if (m_commonSurf->GetDD3Surface() != nullptr) { + Logger::debug("DDraw4Surface::QueryInterface: Query for existing IDirectDrawSurface3"); + return m_commonSurf->GetDD3Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Surface::QueryInterface: Query for IDirectDrawSurface3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw3Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonSurf->GetDDSurface(), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface7))) { + if (m_commonSurf->GetDD7Surface() != nullptr) { + Logger::debug("DDraw4Surface::QueryInterface: Query for existing IDirectDrawSurface7"); + return m_commonSurf->GetDD7Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw4Surface::QueryInterface: Query for IDirectDrawSurface7"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw7Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDD7Interface(), nullptr, false)); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::AddAttachedSurface(LPDIRECTDRAWSURFACE4 lpDDSAttachedSurface) { + Logger::debug("<<< DDraw4Surface::AddAttachedSurface: Proxy"); + + if (unlikely(lpDDSAttachedSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + Logger::warn("DDraw4Surface::AddAttachedSurface: Received an unwrapped surface"); + return DDERR_CANNOTATTACHSURFACE; + } + + DDraw4Surface* ddraw4Surface = static_cast(lpDDSAttachedSurface); + + if (unlikely(ddraw4Surface->GetCommonSurface()->IsBackBufferOrFlippable())) + Logger::warn("DDraw4Surface::AddAttachedSurface: Trying to attach a flippable surface"); + + HRESULT hr = m_proxy->AddAttachedSurface(ddraw4Surface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + ddraw4Surface->SetParentSurface(this); + if (likely(ddraw4Surface->GetCommonSurface()->IsDepthStencil())) + m_depthStencil = ddraw4Surface; + + return hr; + } + + // Docs: "The IDirectDrawSurface4::AddOverlayDirtyRect method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw4Surface::AddOverlayDirtyRect(LPRECT lpRect) { + Logger::debug(">>> DDraw4Surface::AddOverlayDirtyRect"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE4 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) { + Logger::debug("<<< DDraw4Surface::Blt: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDraw4Surface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw4Surface::Blt: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw4Surface::Blt: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw4Surface::Blt: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw4Surface::Blt: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + // Forward DDBLT_DEPTHFILL clears to D3D9 if done on the current depth stencil + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_DEPTHFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9DepthStencil(m_d3d9.ptr()))) { + Logger::debug("DDraw4Surface::Blt: Clearing d3d9 depth stencil"); + + HRESULT hrClear; + const float zClear = m_commonSurf->GetNormalizedFloatDepth(lpDDBltFx->dwFillDepth); + + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0, zClear, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_ZBUFFER, 0, zClear, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDraw4Surface::Blt: Failed to clear d3d9 depth"); + } + // Forward DDBLT_COLORFILL clears to D3D9 if done on the current render target + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_COLORFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9RenderTarget(m_d3d9.ptr()))) { + Logger::debug("DDraw4Surface::Blt: Clearing d3d9 render target"); + + HRESULT hrClear; + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDraw4Surface::Blt: Failed to clear d3d9 render target"); + } + + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDraw4Surface::Blt: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->Blt(lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFx); + } else { + DDraw4Surface* ddraw4Surface = static_cast(lpDDSrcSurface); + hr = m_proxy->Blt(lpDestRect, ddraw4Surface->GetProxied(), lpSrcRect, dwFlags, lpDDBltFx); + } + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw4Surface::Blt: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + // Docs: "The IDirectDrawSurface4::BltBatch method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw4Surface::BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) { + Logger::debug(">>> DDraw4Surface::BltBatch"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE4 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) { + Logger::debug("<<< DDraw4Surface::BltFast: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDraw4Surface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw4Surface::BltFast: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw4Surface::BltFast: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw4Surface::BltFast: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw4Surface::BltFast: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDraw4Surface::BltFast: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->BltFast(dwX, dwY, lpDDSrcSurface, lpSrcRect, dwTrans); + } else { + DDraw4Surface* ddraw4Surface = static_cast(lpDDSrcSurface); + hr = m_proxy->BltFast(dwX, dwY, ddraw4Surface->GetProxied(), lpSrcRect, dwTrans); + } + + if (likely(SUCCEEDED(hr))) { + // Textures get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw4Surface::BltFast: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + // This call will only detach DDSCAPS_ZBUFFER type surfaces and will reject anything else. + HRESULT STDMETHODCALLTYPE DDraw4Surface::DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE4 lpDDSAttachedSurface) { + Logger::debug("<<< DDraw4Surface::DeleteAttachedSurface: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + if (unlikely(lpDDSAttachedSurface != nullptr)) { + Logger::warn("DDraw4Surface::DeleteAttachedSurface: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + HRESULT hrProxy = m_proxy->DeleteAttachedSurface(dwFlags, lpDDSAttachedSurface); + + // If lpDDSAttachedSurface is NULL, then all surfaces are detached + if (lpDDSAttachedSurface == nullptr && likely(SUCCEEDED(hrProxy))) + m_depthStencil = nullptr; + + return hrProxy; + } + + DDraw4Surface* ddraw4Surface = static_cast(lpDDSAttachedSurface); + + HRESULT hr = m_proxy->DeleteAttachedSurface(dwFlags, ddraw4Surface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + if (likely(m_depthStencil == ddraw4Surface)) { + ddraw4Surface->ClearParentSurface(); + m_depthStencil = nullptr; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpEnumSurfacesCallback) { + Logger::debug(">>> DDraw4Surface::EnumAttachedSurfaces"); + + if (unlikely(lpEnumSurfacesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + std::vector attachedSurfaces; + // Enumerate all attached surfaces from the underlying DDraw implementation + m_proxy->EnumAttachedSurfaces(reinterpret_cast(&attachedSurfaces), EnumAttachedSurfaces4Callback); + + HRESULT hr = DDENUMRET_OK; + + // Wrap surfaces as needed and perform the actual callback the application is requesting + auto surfaceIt = attachedSurfaces.begin(); + while (surfaceIt != attachedSurfaces.end() && hr == DDENUMRET_OK) { + Com surface4 = surfaceIt->surface4; + + auto attachedSurfaceIter = m_attachedSurfaces.find(surface4.ptr()); + if (unlikely(attachedSurfaceIter == m_attachedSurfaces.end())) { + // Return the already attached depth surface if it exists + if (unlikely(m_depthStencil != nullptr && surface4.ptr() == m_depthStencil->GetProxied())) { + hr = lpEnumSurfacesCallback(m_depthStencil.ref(), &surfaceIt->desc2, lpContext); + } else { + Com ddraw4Surface = new DDraw4Surface(nullptr, std::move(surface4), m_commonIntf->GetDD4Interface(), this, false); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(ddraw4Surface->GetProxied()), + std::forward_as_tuple(ddraw4Surface.ref())); + hr = lpEnumSurfacesCallback(ddraw4Surface.ref(), &surfaceIt->desc2, lpContext); + } + } else { + hr = lpEnumSurfacesCallback(attachedSurfaceIter->second.ref(), &surfaceIt->desc2, lpContext); + } + + ++surfaceIt; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpfnCallback) { + Logger::debug("<<< DDraw4Surface::EnumOverlayZOrders: Proxy"); + return m_proxy->EnumOverlayZOrders(dwFlags, lpContext, lpfnCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::Flip(LPDIRECTDRAWSURFACE4 lpDDSurfaceTargetOverride, DWORD dwFlags) { + if (m_parent == nullptr) { + Logger::debug("*** DDraw4Surface::Flip: Ignoring"); + return DD_OK; + } + + Logger::debug("*** DDraw4Surface::Flip: Presenting"); + + // Lost surfaces are not flippable + HRESULT hr = m_proxy->IsLost(); + if (unlikely(FAILED(hr))) { + Logger::debug("DDraw4Surface::Flip: Lost surface"); + return hr; + } + + if (unlikely(!(m_commonSurf->IsFrontBuffer() || m_commonSurf->IsBackBufferOrFlippable()))) { + Logger::debug("DDraw4Surface::Flip: Unflippable surface"); + return DDERR_NOTFLIPPABLE; + } + + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Non-exclusive mode validations + if (unlikely(m_commonSurf->IsPrimarySurface() && !exclusiveMode)) { + Logger::debug("DDraw4Surface::Flip: Primary surface flip in non-exclusive mode"); + return DDERR_NOEXCLUSIVEMODE; + } + + // Exclusive mode validations + if (unlikely(m_commonSurf->IsBackBufferOrFlippable() && exclusiveMode)) { + Logger::debug("DDraw4Surface::Flip: Back buffer flip in exclusive mode"); + return DDERR_NOTFLIPPABLE; + } + + Com surf4; + if (m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride)) { + surf4 = static_cast(lpDDSurfaceTargetOverride); + + if (unlikely(!surf4->GetCommonSurface()->IsBackBufferOrFlippable())) { + Logger::debug("DDraw4Surface::Flip: Unflippable override surface"); + return DDERR_NOTFLIPPABLE; + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + m_commonIntf->ResetDrawTracking(); + + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(!IsInitialized())) + InitializeD3D9(m_commonIntf->IsCurrentRenderTarget(this)); + + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride))) { + if (unlikely(lpDDSurfaceTargetOverride != nullptr)) { + Logger::warn("DDraw4Surface::Flip: Received an unwrapped surface"); + return DDERR_GENERIC; + } + if (likely(m_commonIntf->IsCurrentRenderTarget(this))) + m_commonIntf->SetFlipRTSurfaceAndFlags(lpDDSurfaceTargetOverride, dwFlags); + return m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } else { + if (likely(m_commonIntf->IsCurrentRenderTarget(this))) + m_commonIntf->SetFlipRTSurfaceAndFlags(lpDDSurfaceTargetOverride, dwFlags); + return m_proxy->Flip(surf4->GetProxied(), dwFlags); + } + } + + // If the interface is waiting for VBlank and we get a no VSync flip, switch + // to doing immediate presents by resetting the swapchain appropriately + if (unlikely(m_commonIntf->GetWaitForVBlank() && (dwFlags & DDFLIP_NOVSYNC))) { + Logger::info("DDraw4Surface::Flip: Switching to D3DPRESENT_INTERVAL_IMMEDIATE for presentation"); + + d3d9::D3DPRESENT_PARAMETERS resetParams = m_commonIntf->GetPresentParameters(); + resetParams.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + HRESULT hrReset = m_commonIntf->ResetD3D9Swapchain(&resetParams); + if (unlikely(FAILED(hrReset))) { + Logger::warn("DDraw4Surface::Flip: Failed D3D9 swapchain reset"); + } else { + m_commonIntf->SetWaitForVBlank(false); + } + // If the interface is not waiting for VBlank and we stop getting DDFLIP_NOVSYNC + // flags, reset the swapchain in order to return to VSync presentation using DEFAULT + } else if (unlikely(!m_commonIntf->GetWaitForVBlank() && IsVSyncFlipFlag(dwFlags))) { + Logger::info("DDraw4Surface::Flip: Switching to D3DPRESENT_INTERVAL_DEFAULT for presentation"); + + d3d9::D3DPRESENT_PARAMETERS resetParams = m_commonIntf->GetPresentParameters(); + resetParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + HRESULT hrReset = m_commonIntf->ResetD3D9Swapchain(&resetParams); + if (unlikely(FAILED(hrReset))) { + Logger::warn("DDraw4Surface::Flip: Failed D3D9 swapchain reset"); + } else { + m_commonIntf->SetWaitForVBlank(true); + } + } + + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + // If we don't have a valid D3D6 device, this means a D3D7 application + // is trying to flip the surface. Allow that for compatibility reasons. + } else { + Logger::debug("<<< DDraw4Surface::Flip: Proxy"); + + // Update the VBlank wait status based on the flip flags + m_commonIntf->SetWaitForVBlank(IsVSyncFlipFlag(dwFlags)); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride))) { + m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } else { + m_proxy->Flip(surf4->GetProxied(), dwFlags); + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetAttachedSurface(LPDDSCAPS2 lpDDSCaps, LPDIRECTDRAWSURFACE4 *lplpDDAttachedSurface) { + Logger::debug("<<< DDraw4Surface::GetAttachedSurface: Proxy"); + + if (unlikely(lpDDSCaps == nullptr || lplpDDAttachedSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (lpDDSCaps->dwCaps & DDSCAPS_PRIMARYSURFACE) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for the primary surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FRONTBUFFER) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for the front buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_BACKBUFFER) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for the back buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FLIP) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for a flippable surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OFFSCREENPLAIN) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for an offscreen plain surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_ZBUFFER) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for a depth stencil"); + else if ((lpDDSCaps->dwCaps & DDSCAPS_MIPMAP) + || (lpDDSCaps->dwCaps2 & DDSCAPS2_MIPMAPSUBLEVEL)) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for a texture mip map"); + else if (lpDDSCaps->dwCaps & DDSCAPS_TEXTURE) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for a texture"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OVERLAY) + Logger::debug("DDraw4Surface::GetAttachedSurface: Querying for an overlay"); + + Com surface; + HRESULT hr = m_proxy->GetAttachedSurface(lpDDSCaps, &surface); + + // These are rather common, as some games query expecting to get nothing in return, for + // example it's a common use case to query the mip attach chain until nothing is returned + if (FAILED(hr)) { + Logger::debug("DDraw4Surface::GetAttachedSurface: Failed to find the requested surface"); + *lplpDDAttachedSurface = surface.ptr(); + return hr; + } + + try { + auto attachedSurfaceIter = m_attachedSurfaces.find(surface.ptr()); + if (unlikely(attachedSurfaceIter == m_attachedSurfaces.end())) { + // Return the already attached depth surface if it exists + if (unlikely(m_depthStencil != nullptr && surface.ptr() == m_depthStencil->GetProxied())) { + *lplpDDAttachedSurface = m_depthStencil.ref(); + } else { + Com ddraw4Surface = new DDraw4Surface(nullptr, std::move(surface), m_commonIntf->GetDD4Interface(), this, false); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(ddraw4Surface->GetProxied()), + std::forward_as_tuple(ddraw4Surface.ref())); + *lplpDDAttachedSurface = ddraw4Surface.ref(); + } + } else { + *lplpDDAttachedSurface = attachedSurfaceIter->second.ref(); + } + } catch (const DxvkError& e) { + Logger::err(e.message()); + *lplpDDAttachedSurface = nullptr; + return DDERR_GENERIC; + } + + return DD_OK; + } + + // Blitting can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetBltStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw4Surface::GetBltStatus: Proxy"); + m_proxy->GetBltStatus(dwFlags); + } + + Logger::debug(">>> DDraw4Surface::GetBltStatus"); + + if (likely(dwFlags == DDGBS_CANBLT || dwFlags == DDGBS_ISBLTDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetCaps(LPDDSCAPS2 lpDDSCaps) { + Logger::debug(">>> DDraw4Surface::GetCaps"); + + if (unlikely(lpDDSCaps == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDSCaps = m_commonSurf->GetDesc2()->ddsCaps; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper) { + Logger::debug(">>> DDraw4Surface::GetClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + DDrawClipper* clipper = m_commonSurf->GetClipper(); + + if (unlikely(clipper == nullptr)) + return DDERR_NOCLIPPERATTACHED; + + *lplpDDClipper = ref(clipper); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDraw4Surface::GetColorKey: Proxy"); + return m_proxy->GetColorKey(dwFlags, lpDDColorKey); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetDC(HDC *lphDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(lphDC == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lphDC); + + // Foward GetDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDraw4Surface::GetDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw4Surface::GetDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_d3d9->GetDC(lphDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDraw4Surface::GetDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDraw4Surface::GetDC: Proxy"); + return m_proxy->GetDC(lphDC); + } + + // Flipping can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetFlipStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw4Surface::GetFlipStatus: Proxy"); + m_proxy->GetFlipStatus(dwFlags); + } + + Logger::debug(">>> DDraw4Surface::GetFlipStatus"); + + if (likely(dwFlags == DDGFS_CANFLIP || dwFlags == DDGFS_ISFLIPDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetOverlayPosition(LPLONG lplX, LPLONG lplY) { + Logger::debug("<<< DDraw4Surface::GetOverlayPosition: Proxy"); + return m_proxy->GetOverlayPosition(lplX, lplY); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette) { + Logger::debug(">>> DDraw4Surface::GetPalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + DDrawPalette* palette = m_commonSurf->GetPalette(); + + if (unlikely(palette == nullptr)) + return DDERR_NOPALETTEATTACHED; + + *lplpDDPalette = ref(palette); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat) { + Logger::debug(">>> DDraw4Surface::GetPixelFormat"); + + if (unlikely(lpDDPixelFormat == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDPixelFormat = m_commonSurf->GetDesc2()->ddpfPixelFormat; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetSurfaceDesc(LPDDSURFACEDESC2 lpDDSurfaceDesc) { + Logger::debug(">>> DDraw4Surface::GetSurfaceDesc"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpDDSurfaceDesc->dwSize != sizeof(DDSURFACEDESC2))) + return DDERR_INVALIDPARAMS; + + *lpDDSurfaceDesc = *m_commonSurf->GetDesc2(); + + return DD_OK; + } + + // According to the docs: "Because the DirectDrawSurface object is initialized + // when it's created, this method always returns DDERR_ALREADYINITIALIZED." + HRESULT STDMETHODCALLTYPE DDraw4Surface::Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC2 lpDDSurfaceDesc) { + Logger::debug(">>> DDraw4Surface::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::IsLost() { + Logger::debug("<<< DDraw4Surface::IsLost: Proxy"); + return m_proxy->IsLost(); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::Lock(LPRECT lpDestRect, LPDDSURFACEDESC2 lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) { + Logger::debug("<<< DDraw4Surface::Lock: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (unlikely(m_commonSurf->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw4Surface::Lock: Surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !IsInitialized())) + InitializeOrUploadD3D9(); + + if (likely(IsInitialized())) + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw4Surface::Lock: Surface is a swapchain surface"); + } + } else if (unlikely(m_commonSurf->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw4Surface::Lock: Surface is a depth stencil"); + + if (likely(IsInitialized())) + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw4Surface::Lock: Surface is a depth stencil"); + } + } + + return m_proxy->Lock(lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::ReleaseDC(HDC hDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + // Foward ReleaseDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDraw4Surface::ReleaseDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw4Surface::ReleaseDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_d3d9->ReleaseDC(hDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDraw4Surface::ReleaseDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDraw4Surface::ReleaseDC: Proxy"); + + HRESULT hr = m_proxy->ReleaseDC(hDC); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (m_commonSurf->IsTexture()) { + m_commonSurf->DirtyMipMaps(); + } else if (unlikely(m_commonIntf->GetOptions()->apitraceMode)) { + // We should ideally upload the surface contents here at all times, + // however some games are amazing, and do hundreds of locks on the same + // surface per frame, so this would absolutely tank performance + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw4Surface::ReleaseDC: Failed upload to d3d9 surface"); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::Restore() { + Logger::debug("<<< DDraw4Surface::Restore: Proxy"); + return m_proxy->Restore(); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper) { + Logger::debug("<<< DDraw4Surface::SetClipper: Proxy"); + + // A nullptr lpDDClipper gets the current clipper detached + if (lpDDClipper == nullptr) { + HRESULT hr = m_proxy->SetClipper(lpDDClipper); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(nullptr); + } else { + DDrawClipper* ddrawClipper = static_cast(lpDDClipper); + + HRESULT hr = m_proxy->SetClipper(ddrawClipper->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(ddrawClipper); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDraw4Surface::SetColorKey: Proxy"); + + HRESULT hr = m_proxy->SetColorKey(dwFlags, lpDDColorKey); + if (unlikely(FAILED(hr))) + return hr; + + hr = m_commonSurf->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::err("DDraw4Surface::SetColorKey: Failed to retrieve updated surface desc"); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::SetOverlayPosition(LONG lX, LONG lY) { + Logger::debug("<<< DDraw4Surface::SetOverlayPosition: Proxy"); + return m_proxy->SetOverlayPosition(lX, lY); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) { + Logger::debug("<<< DDraw4Surface::SetPalette: Proxy"); + + // A nullptr lpDDPalette gets the current palette detached + if (lpDDPalette == nullptr) { + HRESULT hr = m_proxy->SetPalette(lpDDPalette); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(nullptr); + } else { + DDrawPalette* ddrawPalette = static_cast(lpDDPalette); + + HRESULT hr = m_proxy->SetPalette(ddrawPalette->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(ddrawPalette); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::Unlock(LPRECT lpSurfaceData) { + Logger::debug("<<< DDraw4Surface::Unlock: Proxy"); + + HRESULT hr = m_proxy->Unlock(lpSurfaceData); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (!m_commonSurf->IsTexture()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw4Surface::Unlock: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE4 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) { + Logger::debug("<<< DDraw4Surface::UpdateOverlay: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDDestSurface))) { + Logger::warn("DDraw4Surface::UpdateOverlay: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw4Surface* ddraw4Surface = static_cast(lpDDDestSurface); + return m_proxy->UpdateOverlay(lpSrcRect, ddraw4Surface->GetProxied(), lpDestRect, dwFlags, lpDDOverlayFx); + } + + // Docs: "The IDirectDrawSurface4::UpdateOverlayDisplay method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw4Surface::UpdateOverlayDisplay(DWORD dwFlags) { + Logger::debug(">>> DDraw4Surface::UpdateOverlayDisplay"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE4 lpDDSReference) { + Logger::debug("<<< DDraw4Surface::UpdateOverlayZOrder: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSReference))) { + Logger::warn("DDraw4Surface::UpdateOverlayZOrder: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw4Surface* ddraw4Surface = static_cast(lpDDSReference); + return m_proxy->UpdateOverlayZOrder(dwFlags, ddraw4Surface->GetProxied()); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetDDInterface(LPVOID *lplpDD) { + if (unlikely(m_commonIntf->GetDD4Interface() == nullptr)) { + Logger::warn("<<< DDraw4Surface::GetDDInterface: Proxy"); + return m_proxy->GetDDInterface(lplpDD); + } + + Logger::debug(">>> DDraw4Surface::GetDDInterface"); + + if (unlikely(lplpDD == nullptr)) + return DDERR_INVALIDPARAMS; + + // Was an easy footgun to return a proxied interface + *lplpDD = ref(m_commonIntf->GetDD4Interface()); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::PageLock(DWORD dwFlags) { + Logger::debug("<<< DDraw4Surface::PageLock: Proxy"); + return m_proxy->PageLock(dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::PageUnlock(DWORD dwFlags) { + Logger::debug("<<< DDraw4Surface::PageUnlock: Proxy"); + return m_proxy->PageUnlock(dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::SetSurfaceDesc(LPDDSURFACEDESC2 lpDDSD, DWORD dwFlags) { + Logger::debug("<<< DDraw4Surface::SetSurfaceDesc: Proxy"); + + // Can be used only to set the surface data and pixel format + // used by an explicit system-memory surface (will be validated) + HRESULT hr = m_proxy->SetSurfaceDesc(lpDDSD, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + hr = m_commonSurf->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::err("DDraw4Surface::SetSurfaceDesc: Failed to retrieve updated surface desc"); + + // We may need to recreate the d3d9 object based on the new desc + m_d3d9 = nullptr; + + if (!m_commonSurf->IsTexture()) { + InitializeOrUploadD3D9(); + } else { + m_commonSurf->DirtyMipMaps(); + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::SetPrivateData(REFGUID tag, LPVOID pData, DWORD cbSize, DWORD dwFlags) { + Logger::debug("<<< DDraw4Surface::SetPrivateData: Proxy"); + return m_proxy->SetPrivateData(tag, pData, cbSize, dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetPrivateData(REFGUID tag, LPVOID pBuffer, LPDWORD pcbBufferSize) { + Logger::debug("<<< DDraw4Surface::GetPrivateData: Proxy"); + return m_proxy->GetPrivateData(tag, pBuffer, pcbBufferSize); + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::FreePrivateData(REFGUID tag) { + Logger::debug("<<< DDraw4Surface::FreePrivateData: Proxy"); + return m_proxy->FreePrivateData(tag); + } + + // Docs: "The only defined uniqueness value is 0, indicating that the surface + // is likely to be changing beyond the control of DirectDraw." + HRESULT STDMETHODCALLTYPE DDraw4Surface::GetUniquenessValue(LPDWORD pValue) { + Logger::debug(">>> DDraw4Surface::GetUniquenessValue"); + + if (unlikely(pValue == nullptr)) + return DDERR_INVALIDPARAMS; + + *pValue = 0; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw4Surface::ChangeUniquenessValue() { + Logger::debug(">>> DDraw4Surface::ChangeUniquenessValue"); + return DD_OK; + } + + HRESULT DDraw4Surface::InitializeD3D9RenderTarget() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (unlikely(!IsInitialized())) + hr = InitializeD3D9(true); + + return hr; + } + + HRESULT DDraw4Surface::InitializeD3D9DepthStencil() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (unlikely(!IsInitialized())) + hr = InitializeD3D9(false); + + return hr; + } + + HRESULT DDraw4Surface::InitializeOrUploadD3D9() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (likely(IsInitialized())) { + hr = UploadSurfaceData(); + } else { + hr = InitializeD3D9(false); + } + + return hr; + } + + inline HRESULT DDraw4Surface::InitializeD3D9(const bool initRT) { + if (unlikely(m_d3d9Device == nullptr)) { + Logger::debug("DDraw4Surface::InitializeD3D9: Null device, can't initialize right now"); + return DD_OK; + } + + Logger::debug(str::format("DDraw4Surface::InitializeD3D9: Initializing nr. [[4-", m_surfCount, "]]")); + + const DDSURFACEDESC2* desc2 = m_commonSurf->GetDesc2(); + const d3d9::D3DFORMAT format = m_commonSurf->GetD3D9Format(); + + if (unlikely(desc2->dwHeight == 0 || desc2->dwWidth == 0)) { + Logger::err("DDraw4Surface::InitializeD3D9: Surface has 0 height or width"); + return DDERR_GENERIC; + } + + if (unlikely(format == d3d9::D3DFMT_UNKNOWN)) { + Logger::err("DDraw4Surface::InitializeD3D9: Surface has an unknown format"); + return DDERR_GENERIC; + } + + // Don't initialize P8 textures/surfaces since we don't support them. + // Some applications do require them to be created by ddraw, otherwise + // they will simply fail to start, so just ignore them for now. + if (unlikely(format == d3d9::D3DFMT_P8)) { + static bool s_formatP8ErrorShown; + + if (!std::exchange(s_formatP8ErrorShown, true)) + Logger::warn("DDraw4Surface::InitializeD3D9: Unsupported format D3DFMT_P8"); + + return DD_OK; + + // Similarly, D3DFMT_R3G3B2 isn't supported by D3D9 dxvk, however some + // applications require it to be supported by ddraw, even if they do not + // use it. Simply ignore any D3DFMT_R3G3B2 textures/surfaces for now. + } else if (unlikely(format == d3d9::D3DFMT_R3G3B2)) { + static bool s_formatR3G3B2ErrorShown; + + if (!std::exchange(s_formatR3G3B2ErrorShown, true)) + Logger::warn("DDraw4Surface::InitializeD3D9: Unsupported format D3DFMT_R3G3B2"); + + return DD_OK; + } + + // We need to count the number of actual mips on initialization by going through + // the mip chain, since the dwMipMapCount number may or may not be accurate. I am + // guessing it was intended more as a hint, not neceesarily a set number. + if (m_commonSurf->IsTexture()) { + IDirectDrawSurface4* mipMap = m_proxy.ptr(); + DDSURFACEDESC2 mipDesc2; + uint16_t mipCount = 1; + + while (mipMap != nullptr) { + IDirectDrawSurface4* parentSurface = mipMap; + mipMap = nullptr; + parentSurface->EnumAttachedSurfaces(&mipMap, ListMipChainSurfaces4Callback); + if (mipMap != nullptr) { + mipCount++; + + mipDesc2 = { }; + mipDesc2.dwSize = sizeof(DDSURFACEDESC2); + mipMap->GetSurfaceDesc(&mipDesc2); + // Ignore multiple 1x1 mips, which apparently can get generated if the + // application gets the dwMipMapCount wrong vs surface dimensions. + if (unlikely(mipDesc2.dwWidth == 1 && mipDesc2.dwHeight == 1)) + break; + } + } + + // Do not worry about maximum supported mip map levels validation, + // because D3D9 will handle this for us and cap them appropriately + if (mipCount > 1) { + Logger::debug(str::format("DDraw4Surface::InitializeD3D9: Found ", mipCount, " mip levels")); + + if (unlikely(mipCount != desc2->dwMipMapCount)) + Logger::debug(str::format("DDraw4Surface::InitializeD3D9: Mismatch with declared ", desc2->dwMipMapCount, " mip levels")); + } + + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) { + Logger::debug("DDraw4Surface::InitializeD3D9: Using auto mip map generation"); + mipCount = 0; + } + + m_commonSurf->SetMipCount(mipCount); + } + + d3d9::D3DPOOL pool = d3d9::D3DPOOL_DEFAULT; + DWORD usage = 0; + + // General surface/texture pool placement + if (desc2->ddsCaps.dwCaps & DDSCAPS_LOCALVIDMEM) + pool = d3d9::D3DPOOL_DEFAULT; + // There's no explicit non-local video memory placement + // per se, but D3DPOOL_MANAGED is close enough + else if ((desc2->ddsCaps.dwCaps & DDSCAPS_NONLOCALVIDMEM) || (desc2->ddsCaps.dwCaps2 & DDSCAPS2_TEXTUREMANAGE)) + pool = d3d9::D3DPOOL_MANAGED; + else if (desc2->ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY) + // We can't know beforehand if a texture is or isn't going to be + // used in SetTexture() calls, and textures placed in D3DPOOL_SYSTEMMEM + // will not work in that context in dxvk, so revert to D3DPOOL_MANAGED. + pool = m_commonSurf->IsTexture() ? d3d9::D3DPOOL_MANAGED : d3d9::D3DPOOL_SYSTEMMEM; + + // Place all possible render targets in DEFAULT + // + // Note: This is somewhat problematic for textures and cube maps + // which will have D3DUSAGE_RENDERTARGET, but also need to have + // D3DUSAGE_DYNAMIC for locking/uploads to work. The flag combination + // isn't supported in D3D9, but we have a D3D7 exception in place. + // + if (m_commonSurf->IsRenderTarget() || initRT) { + Logger::debug("DDraw4Surface::InitializeD3D9: Usage: D3DUSAGE_RENDERTARGET"); + pool = d3d9::D3DPOOL_DEFAULT; + usage |= D3DUSAGE_RENDERTARGET; + } + // All depth stencils will be created in DEFAULT + if (m_commonSurf->IsDepthStencil()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Usage: D3DUSAGE_DEPTHSTENCIL"); + pool = d3d9::D3DPOOL_DEFAULT; + usage |= D3DUSAGE_DEPTHSTENCIL; + } + + // General usage flags + if (m_commonSurf->IsTexture()) { + if (pool == d3d9::D3DPOOL_DEFAULT) { + Logger::debug("DDraw4Surface::InitializeD3D9: Usage: D3DUSAGE_DYNAMIC"); + usage |= D3DUSAGE_DYNAMIC; + } + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) { + Logger::debug("DDraw4Surface::InitializeD3D9: Usage: D3DUSAGE_AUTOGENMIPMAP"); + usage |= D3DUSAGE_AUTOGENMIPMAP; + } + } + + const char* poolPlacement = pool == d3d9::D3DPOOL_DEFAULT ? "D3DPOOL_DEFAULT" : + pool == d3d9::D3DPOOL_SYSTEMMEM ? "D3DPOOL_SYSTEMMEM" : "D3DPOOL_MANAGED"; + + Logger::debug(str::format("DDraw4Surface::InitializeD3D9: Placing in: ", poolPlacement)); + + // Use the MSAA type that was determined to be supported during device creation + const d3d9::D3DMULTISAMPLE_TYPE multiSampleType = m_commonIntf->GetMultiSampleType(); + const uint32_t index = m_commonSurf->GetBackBufferIndex(); + + Com surf; + + HRESULT hr = DDERR_GENERIC; + + // Front Buffer + if (m_commonSurf->IsFrontBuffer()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Initializing front buffer..."); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to retrieve front buffer"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Back Buffer + } else if (m_commonSurf->IsBackBufferOrFlippable()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Initializing back buffer..."); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to retrieve back buffer"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Textures + } else if (m_commonSurf->IsTexture()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Initializing texture..."); + + Com tex; + + hr = m_d3d9Device->CreateTexture( + desc2->dwWidth, desc2->dwHeight, m_commonSurf->GetMipCount(), usage, + format, pool, &tex, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to create texture"); + m_texture9 = nullptr; + return hr; + } + + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) + tex->SetAutoGenFilterType(d3d9::D3DTEXF_ANISOTROPIC); + + // Attach level 0 to this surface + tex->GetSurfaceLevel(0, &surf); + m_d3d9 = (std::move(surf)); + + Logger::debug("DDraw4Surface::InitializeD3D9: Created texture"); + m_texture9 = std::move(tex); + + // Depth Stencil + } else if (m_commonSurf->IsDepthStencil()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Initializing depth stencil..."); + + hr = m_d3d9Device->CreateDepthStencilSurface( + desc2->dwWidth, desc2->dwHeight, format, + multiSampleType, 0, FALSE, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to create DS"); + m_d3d9 = nullptr; + return hr; + } + + Logger::debug("DDraw4Surface::InitializeD3D9: Created depth stencil surface"); + + m_d3d9 = std::move(surf); + + // Offscreen Plain Surfaces + } else if (m_commonSurf->IsOffScreenPlainSurface()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Initializing offscreen plain surface..."); + + // Sometimes we get passed offscreen plain surfaces which should be tied to the back buffer, + // either as existing RTs or during SetRenderTarget() calls, which are tracked with initRT + if (unlikely(m_commonIntf->IsCurrentRenderTarget(this) || initRT)) { + Logger::debug("DDraw4Surface::InitializeD3D9: Offscreen plain surface is the RT"); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to retrieve offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + } else { + hr = m_d3d9Device->CreateOffscreenPlainSurface( + desc2->dwWidth, desc2->dwHeight, format, + pool, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to create offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + } + + m_d3d9 = std::move(surf); + + // Overlays (haven't seen any actual use of overlays in the wild) + } else if (m_commonSurf->IsOverlay()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Initializing overlay..."); + + // Always link overlays to the back buffer + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to retrieve overlay surface"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Generic render target + } else if (m_commonSurf->IsRenderTarget()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Initializing render target..."); + + // Must be lockable for blitting to work. Note that + // D3D9 does not allow the creation of lockable RTs when + // using MSAA, but we have a D3D7 exception in place. + hr = m_d3d9Device->CreateRenderTarget( + desc2->dwWidth, desc2->dwHeight, format, + multiSampleType, usage, TRUE, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to create render target"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // We sometimes get generic surfaces, with only dimensions, format and placement info + } else if (!m_commonSurf->IsNotKnown()) { + Logger::debug("DDraw4Surface::InitializeD3D9: Initializing generic surface..."); + + hr = m_d3d9Device->CreateOffscreenPlainSurface( + desc2->dwWidth, desc2->dwHeight, format, + pool, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw4Surface::InitializeD3D9: Failed to create offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + + Logger::debug("DDraw4Surface::InitializeD3D9: Created offscreen plain surface"); + + m_d3d9 = std::move(surf); + } else { + Logger::warn("DDraw4Surface::InitializeD3D9: Skipping initialization of unknown surface"); + } + + // Depth stencils will not need uploads post initialization + if (likely(!m_commonSurf->IsDepthStencil())) + UploadSurfaceData(); + + return DD_OK; + } + + inline HRESULT DDraw4Surface::UploadSurfaceData() { + Logger::debug(str::format("DDraw4Surface::UploadSurfaceData: Uploading nr. [[4-", m_surfCount, "]]")); + + if (unlikely(m_commonIntf->HasDrawn() && m_commonSurf->IsGuardableSurface())) { + Logger::debug("DDraw4Surface::UploadSurfaceData: Skipping upload"); + return DD_OK; + } + + const d3d9::D3DFORMAT format = m_commonSurf->GetD3D9Format(); + + if (m_commonSurf->IsTexture()) { + BlitToD3D9Texture(m_texture9.ptr(), format, + m_proxy.ptr(), m_commonSurf->GetMipCount()); + // Blit surfaces directly + } else { + if (unlikely(m_commonSurf->IsDepthStencil())) { + if (likely(m_commonIntf->GetOptions()->uploadDepthStencils)) { + Logger::debug("DDraw4Surface::UploadSurfaceData: Uploading depth stencil"); + } else { + Logger::debug("DDraw4Surface::UploadSurfaceData: Skipping upload of depth stencil"); + return DD_OK; + } + } + + BlitToD3D9Surface(m_d3d9.ptr(), format, m_proxy.ptr()); + } + + return DD_OK; + } + +} diff --git a/src/ddraw/ddraw4/ddraw4_surface.h b/src/ddraw/ddraw4/ddraw4_surface.h new file mode 100644 index 00000000000..6bb28cc5072 --- /dev/null +++ b/src/ddraw/ddraw4/ddraw4_surface.h @@ -0,0 +1,222 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../ddraw_common_interface.h" +#include "../ddraw_common_surface.h" + +#include "ddraw4_interface.h" + +#include "../d3d3/d3d3_texture.h" +#include "../d3d6/d3d6_texture.h" + +#include + +namespace dxvk { + + class DDraw7Surface; + + /** + * \brief IDirectDrawSurface4 interface implementation + */ + class DDraw4Surface final : public DDrawWrappedObject { + + public: + + DDraw4Surface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDraw4Interface* pParent, + DDraw4Surface* pParentSurf, + bool isChildObject); + + ~DDraw4Surface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE AddAttachedSurface(LPDIRECTDRAWSURFACE4 lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE AddOverlayDirtyRect(LPRECT lpRect); + + HRESULT STDMETHODCALLTYPE Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE4 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx); + + HRESULT STDMETHODCALLTYPE BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE4 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans); + + HRESULT STDMETHODCALLTYPE DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE4 lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpfnCallback); + + HRESULT STDMETHODCALLTYPE Flip(LPDIRECTDRAWSURFACE4 lpDDSurfaceTargetOverride, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetAttachedSurface(LPDDSCAPS2 lpDDSCaps, LPDIRECTDRAWSURFACE4 *lplpDDAttachedSurface); + + HRESULT STDMETHODCALLTYPE GetBltStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDSCAPS2 lpDDSCaps); + + HRESULT STDMETHODCALLTYPE GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper); + + HRESULT STDMETHODCALLTYPE GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE GetDC(HDC *lphDC); + + HRESULT STDMETHODCALLTYPE GetFlipStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetOverlayPosition(LPLONG lplX, LPLONG lplY); + + HRESULT STDMETHODCALLTYPE GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette); + + HRESULT STDMETHODCALLTYPE GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat); + + HRESULT STDMETHODCALLTYPE GetSurfaceDesc(LPDDSURFACEDESC2 lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC2 lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE IsLost(); + + HRESULT STDMETHODCALLTYPE Lock(LPRECT lpDestRect, LPDDSURFACEDESC2 lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent); + + HRESULT STDMETHODCALLTYPE ReleaseDC(HDC hDC); + + HRESULT STDMETHODCALLTYPE Restore(); + + HRESULT STDMETHODCALLTYPE SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper); + + HRESULT STDMETHODCALLTYPE SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE SetOverlayPosition(LONG lX, LONG lY); + + HRESULT STDMETHODCALLTYPE SetPalette(LPDIRECTDRAWPALETTE lpDDPalette); + + HRESULT STDMETHODCALLTYPE Unlock(LPRECT lpSurfaceData); + + HRESULT STDMETHODCALLTYPE UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE4 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx); + + HRESULT STDMETHODCALLTYPE UpdateOverlayDisplay(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE4 lpDDSReference); + + HRESULT STDMETHODCALLTYPE GetDDInterface(LPVOID *lplpDD); + + HRESULT STDMETHODCALLTYPE PageLock(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE PageUnlock(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetSurfaceDesc(LPDDSURFACEDESC2 lpDDSD, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID tag, LPVOID pData, DWORD cbSize, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID tag, LPVOID pBuffer, LPDWORD pcbBufferSize); + + HRESULT STDMETHODCALLTYPE FreePrivateData(REFGUID tag); + + HRESULT STDMETHODCALLTYPE GetUniquenessValue(LPDWORD pValue); + + HRESULT STDMETHODCALLTYPE ChangeUniquenessValue(); + + DDrawCommonSurface* GetCommonSurface() const { + return m_commonSurf.ptr(); + } + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + d3d9::IDirect3DDevice9* GetD3D9Device() const { + return m_d3d9Device; + } + + d3d9::IDirect3DTexture9* GetD3D9Texture() const { + return m_texture9.ptr(); + } + + DDraw4Surface* GetAttachedDepthStencil() { + // Fast path, since in most cases we already store the required surface + if (likely(m_depthStencil.ptr() != nullptr)) + return m_depthStencil.ptr(); + + DDSCAPS2 caps2; + caps2.dwCaps = DDSCAPS_ZBUFFER; + IDirectDrawSurface4* surface = nullptr; + HRESULT hr = GetAttachedSurface(&caps2, &surface); + if (unlikely(FAILED(hr))) + return nullptr; + + m_depthStencil = reinterpret_cast(surface); + + return m_depthStencil.ptr(); + } + + void ClearAttachedDepthStencil() { + m_depthStencil = nullptr; + } + + void SetParentSurface(DDraw4Surface* surface) { + m_parentSurf = surface; + m_commonSurf->SetIsAttached(true); + } + + void ClearParentSurface() { + m_parentSurf = nullptr; + m_commonSurf->SetIsAttached(false); + } + + HRESULT InitializeD3D9RenderTarget(); + + HRESULT InitializeD3D9DepthStencil(); + + HRESULT InitializeOrUploadD3D9(); + + private: + + inline HRESULT InitializeD3D9(const bool initRT); + + inline HRESULT UploadSurfaceData(); + + inline void RefreshD3D9Device() { + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (unlikely(m_d3d9Device != d3d9Device)) { + // Check if the device has been recreated and reset all D3D9 resources + if (m_d3d9Device != nullptr) { + Logger::debug("DDraw4Surface: Device context has changed, clearing all D3D9 resources"); + m_texture9 = nullptr; + m_d3d9 = nullptr; + } + m_d3d9Device = d3d9Device; + } + } + + bool m_isChildObject = true; + + static uint32_t s_surfCount; + uint32_t m_surfCount = 0; + + Com m_commonSurf; + DDrawCommonInterface* m_commonIntf = nullptr; + + DDraw4Surface* m_parentSurf = nullptr; + + d3d9::IDirect3DDevice9* m_d3d9Device = nullptr; + + Com m_texture6; + + Com m_texture9; + + // Back buffers will have depth stencil surfaces as attachments (in practice + // I have never seen more than one depth stencil being attached at a time) + Com m_depthStencil; + + // These are attached surfaces, which are typically mips or other types of generated + // surfaces, which need to exist for the entire lifecycle of their parent surface. + // They are implemented with linked list, so for example only one mip level + // will be held in a parent texture, and the next mip level will be held in the previous mip. + std::unordered_map> m_attachedSurfaces; + + }; + +} diff --git a/src/ddraw/ddraw7/ddraw7_interface.cpp b/src/ddraw/ddraw7/ddraw7_interface.cpp new file mode 100644 index 00000000000..9cc0beedb34 --- /dev/null +++ b/src/ddraw/ddraw7/ddraw7_interface.cpp @@ -0,0 +1,778 @@ +#include "ddraw7_interface.h" + +#include "ddraw7_surface.h" + +#include "../ddraw_clipper.h" +#include "../ddraw_palette.h" + +#include "../ddraw/ddraw_interface.h" +#include "../ddraw2/ddraw2_interface.h" +#include "../ddraw4/ddraw4_interface.h" + +#include "../d3d7/d3d7_interface.h" +#include "../d3d7/d3d7_device.h" +#include + +namespace dxvk { + + uint32_t DDraw7Interface::s_intfCount = 0; + + DDraw7Interface::DDraw7Interface( + DDrawCommonInterface* commonIntf, + Com&& proxyIntf) + : DDrawWrappedObject(nullptr, std::move(proxyIntf), nullptr) + , m_commonIntf ( commonIntf ) { + // We need a temporary D3D9 interface at this point to retrieve the + // adapter identifier, as well as (potentially) the options through a bridge + Com d3d9Intf = d3d9::Direct3DCreate9(D3D_SDK_VERSION); + + d3d9::D3DADAPTER_IDENTIFIER9 adapterIdentifier9; + HRESULT hr = d3d9Intf->GetAdapterIdentifier(0, 0, &adapterIdentifier9); + if (unlikely(FAILED(hr))) { + throw DxvkError("DDraw7Interface: ERROR! Failed to get D3D9 adapter identifier!"); + } + + if (m_commonIntf == nullptr) { + Com d3d9Bridge; + // Can never fail while we statically link the d3d9 module + d3d9Intf->QueryInterface(__uuidof(IDxvkD3D8InterfaceBridge), reinterpret_cast(&d3d9Bridge)); + + m_commonIntf = new DDrawCommonInterface(D3DOptions(*d3d9Bridge->GetConfig())); + } + + m_commonIntf->SetAdapterIdentifier(adapterIdentifier9); + + if (m_commonIntf->GetOrigin() == nullptr) + m_commonIntf->SetOrigin(this); + + m_commonIntf->SetDD7Interface(this); + + static bool s_apitraceModeWarningShown; + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && + !std::exchange(s_apitraceModeWarningShown, true))) + Logger::warn("DDraw7Interface: Apitrace mode is enabled. Performance will be suboptimal!"); + + m_intfCount = ++s_intfCount; + + Logger::debug(str::format("DDraw7Interface: Created a new interface nr. <<7-", m_intfCount, ">>")); + } + + DDraw7Interface::~DDraw7Interface() { + if (m_commonIntf->GetOrigin() == this) + m_commonIntf->SetOrigin(nullptr); + + m_commonIntf->SetDD7Interface(nullptr); + + Logger::debug(str::format("DDraw7Interface: Interface nr. <<7-", m_intfCount, ">> bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDraw7Interface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + // Standard way of retrieving a D3D7 interface + if (riid == __uuidof(IDirect3D7)) { + Logger::debug("DDraw7Interface::QueryInterface: Query for IDirect3D7"); + + // Initialize the IDirect3D7 interlocked object + if (unlikely(m_d3d7Intf == nullptr)) { + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + m_d3d7Intf = new D3D7Interface(m_commonIntf.ptr(), nullptr, std::move(ppvProxyObject), this); + } + + *ppvObject = m_d3d7Intf.ref(); + + return S_OK; + } + // Some games query for legacy ddraw interfaces + if (unlikely(riid == __uuidof(IDirectDraw))) { + if (m_commonIntf->GetDDInterface() != nullptr) { + Logger::debug("DDraw7Interface::QueryInterface: Query for existing IDirectDraw"); + return m_commonIntf->GetDDInterface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw7Interface::QueryInterface: Query for legacy IDirectDraw"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDrawInterface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDraw2))) { + if (m_commonIntf->GetDD2Interface() != nullptr) { + Logger::debug("DDraw7Interface::QueryInterface: Query for existing IDirectDraw2"); + return m_commonIntf->GetDD2Interface()->QueryInterface(riid, ppvObject); + } + + Logger::warn("DDraw7Interface::QueryInterface: Query for legacy IDirectDraw2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw2Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDraw4))) { + if (m_commonIntf->GetDD4Interface() != nullptr) { + Logger::debug("DDraw7Interface::QueryInterface: Query for existing IDirectDraw4"); + return m_commonIntf->GetDD4Interface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw7Interface::QueryInterface: Query for legacy IDirectDraw4"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw4Interface(m_commonIntf.ptr(), std::move(ppvProxyObject))); + + return S_OK; + } + // Quite a lot of games query for this IID during intro playback + if (unlikely(riid == GUID_IAMMediaStream)) { + Logger::debug("DDraw7Interface::QueryInterface: Query for IAMMediaStream"); + return m_proxy->QueryInterface(riid, ppvObject); + } + // Also seen queried by some games, such as V-Rally 2: Expert Edition + if (unlikely(riid == GUID_IMediaStream)) { + Logger::debug("DDraw7Interface::QueryInterface: Query for IMediaStream"); + return m_proxy->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // The documentation states: "The IDirectDraw7::Compact method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw7Interface::Compact() { + Logger::debug(">>> DDraw7Interface::Compact"); + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::CreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw7Interface::CreateClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + Com lplpDDClipperProxy; + HRESULT hr = m_proxy->CreateClipper(dwFlags, &lplpDDClipperProxy, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + *lplpDDClipper = ref(new DDrawClipper(std::move(lplpDDClipperProxy), this)); + } else { + Logger::warn("DDraw7Interface::CreateClipper: Failed to create proxy clipper"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::CreatePalette(DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw7Interface::CreatePalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + Com lplpDDPaletteProxy; + HRESULT hr = m_proxy->CreatePalette(dwFlags, lpColorTable, &lplpDDPaletteProxy, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + *lplpDDPalette = ref(new DDrawPalette(std::move(lplpDDPaletteProxy), this)); + } else { + Logger::warn("DDraw7Interface::CreatePalette: Failed to create proxy palette"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::CreateSurface(LPDDSURFACEDESC2 lpDDSurfaceDesc, LPDIRECTDRAWSURFACE7 *lplpDDSurface, IUnknown *pUnkOuter) { + Logger::debug(">>> DDraw7Interface::CreateSurface"); + + // The cooperative level is always checked first + if (unlikely(!m_commonIntf->IsCooperativeLevelSet())) + return DDERR_NOCOOPERATIVELEVELSET; + + if (unlikely(lpDDSurfaceDesc == nullptr || lplpDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDSurface); + + // Because we are removing the DDSCAPS_WRITEONLY flag below, we need + // to first validate the combinations that would otherwise cause issues + HRESULT hr = ValidateSurfaceFlags(lpDDSurfaceDesc); + if (unlikely(FAILED(hr))) + return hr; + + // We need to ensure we can always read from surfaces for upload to + // D3D9, so always strip the DDSCAPS_WRITEONLY flag on creation + lpDDSurfaceDesc->ddsCaps.dwCaps &= ~DDSCAPS_WRITEONLY; + // Similarly strip the DDSCAPS2_OPAQUE flag on texture creation + if (lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_TEXTURE) { + lpDDSurfaceDesc->ddsCaps.dwCaps2 &= ~DDSCAPS2_OPAQUE; + } + + if (unlikely((lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_ZBUFFER) + && (lpDDSurfaceDesc->ddpfPixelFormat.dwZBitMask == 0xFFFFFFFF))) { + if (m_commonIntf->GetOptions()->useD24X8forD32) { + // In case of up-front unsupported and unadvertised D32 depth stencil use, + // replace it with D24X8, as some games, such as Sacrifice, rely on it + // to properly enable 32-bit display modes (and revert to 16-bit otherwise) + Logger::info("DDraw7Interface::CreateSurface: Using D24X8 instead of D32"); + lpDDSurfaceDesc->ddpfPixelFormat.dwZBitMask = 0xFFFFFF; + } else { + Logger::warn("DDraw7Interface::CreateSurface: Use of unsupported D32"); + } + } + + Com ddraw7SurfaceProxied; + hr = m_proxy->CreateSurface(lpDDSurfaceDesc, &ddraw7SurfaceProxied, pUnkOuter); + + if (likely(SUCCEEDED(hr))) { + try{ + Com surface7 = new DDraw7Surface(nullptr, std::move(ddraw7SurfaceProxied), this, nullptr, true); + if (unlikely(lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)) + m_commonIntf->SetPrimarySurface(surface7->GetCommonSurface()); + *lplpDDSurface = surface7.ref(); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + // Some games simply try creating surfaces with various formats until something works... + } else { + Logger::debug("DDraw7Interface::CreateSurface: Failed to create proxy surface"); + return hr; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::DuplicateSurface(LPDIRECTDRAWSURFACE7 lpDDSurface, LPDIRECTDRAWSURFACE7 *lplpDupDDSurface) { + Logger::debug("<<< DDraw7Interface::DuplicateSurface: Proxy"); + + if (m_commonIntf->IsWrappedSurface(lpDDSurface)) { + InitReturnPtr(lplpDupDDSurface); + + DDraw7Surface* ddraw7Surface = static_cast(lpDDSurface); + Com dupSurface7; + HRESULT hr = m_proxy->DuplicateSurface(ddraw7Surface->GetProxied(), &dupSurface7); + if (likely(SUCCEEDED(hr))) { + try { + *lplpDupDDSurface = ref(new DDraw7Surface(nullptr, std::move(dupSurface7), this, nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } + return hr; + } else { + if (unlikely(lpDDSurface != nullptr)) { + Logger::warn("DDraw7Interface::DuplicateSurface: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + return m_proxy->DuplicateSurface(lpDDSurface, lplpDupDDSurface); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::EnumDisplayModes(DWORD dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK2 lpEnumModesCallback) { + Logger::debug("<<< DDraw7Interface::EnumDisplayModes: Proxy"); + return m_proxy->EnumDisplayModes(dwFlags, lpDDSurfaceDesc, lpContext, lpEnumModesCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::EnumSurfaces(DWORD dwFlags, LPDDSURFACEDESC2 lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpEnumSurfacesCallback) { + Logger::debug(">>> DDraw7Interface::EnumSurfaces: Proxy"); + + if (unlikely(lpEnumSurfacesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + std::vector attachedSurfaces; + // Enumerate all surfaces from the underlying DDraw implementation + m_proxy->EnumSurfaces(dwFlags, lpDDSD, reinterpret_cast(&attachedSurfaces), EnumAttachedSurfaces7Callback); + + HRESULT hr = DDENUMRET_OK; + + // Wrap surfaces as needed and perform the actual callback the application is requesting + auto surfaceIt = attachedSurfaces.begin(); + while (surfaceIt != attachedSurfaces.end() && hr == DDENUMRET_OK) { + Com surface7 = surfaceIt->surface7; + + Com ddraw7Surface = new DDraw7Surface(nullptr, std::move(surface7), this, nullptr, false); + hr = lpEnumSurfacesCallback(ddraw7Surface.ref(), &surfaceIt->desc2, lpContext); + + ++surfaceIt; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::FlipToGDISurface() { + Logger::debug("*** DDraw7Interface::FlipToGDISurface: Ignoring"); + + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) + return m_proxy->FlipToGDISurface(); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) { + Logger::debug("<<< DDraw7Interface::GetCaps: Proxy"); + + HRESULT hr = m_proxy->GetCaps(lpDDDriverCaps, lpDDHELCaps); + if (unlikely(FAILED(hr))) + return hr; + + static constexpr DWORD Megabytes = 1024 * 1024; + static constexpr DWORD MaxMemory = ddrawCaps::MaxTextureMemory * Megabytes; + static constexpr DWORD ReservedMemory = ddrawCaps::ReservedTextureMemory * Megabytes; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + // Properly fill in the dwVidMemTotal / dwVidMemFree fields + DWORD total9 = 0; + DWORD free9 = 0; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (likely(d3d9Device != nullptr)) { + Logger::debug("DDraw7Interface::GetCaps: Getting memory stats from D3D9"); + + total9 = static_cast(m_commonIntf->GetTotalTextureMemory()); + free9 = static_cast(d3d9Device->GetAvailableTextureMem()); + + if (likely(total9 >= MaxMemory)) { + const DWORD delta = total9 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free9 > delta + ReservedMemory ? free9 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw7Interface::GetCaps: Total: ", total9)); + Logger::debug(str::format("DDraw7Interface::GetCaps: Free : ", free9)); + } else { + Logger::debug("DDraw7Interface::GetCaps: Getting memory stats from DDraw"); + + const DWORD total7 = lpDDDriverCaps != nullptr ? lpDDDriverCaps->dwVidMemTotal : 0; + const DWORD free7 = lpDDDriverCaps != nullptr ? lpDDDriverCaps->dwVidMemFree : 0; + + Logger::debug(str::format("DDraw7Interface::GetCaps: DDraw Total: ", total7)); + Logger::debug(str::format("DDraw7Interface::GetCaps: DDraw Free : ", free7)); + + if (unlikely(total7 < MaxMemory)) { + total9 = total7; + free9 = free7; + } else { + const DWORD delta = total7 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free7 > delta + ReservedMemory ? free7 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw7Interface::GetCaps: Total: ", total9)); + Logger::debug(str::format("DDraw7Interface::GetCaps: Free : ", free9)); + } + + // Report all possible flip capabilities as supported + if (lpDDDriverCaps != nullptr) { + lpDDDriverCaps->dwCaps2 |= DDCAPS2_FLIPINTERVAL | DDCAPS2_FLIPNOVSYNC; + lpDDDriverCaps->dwZBufferBitDepths = d3dOptions->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + lpDDDriverCaps->dwVidMemTotal = total9; + lpDDDriverCaps->dwVidMemFree = free9; + lpDDDriverCaps->dwNumFourCCCodes = ddrawCaps::NumberOfFOURCCCodes; + } + if (lpDDHELCaps != nullptr) { + lpDDHELCaps->dwCaps2 |= DDCAPS2_FLIPINTERVAL | DDCAPS2_FLIPNOVSYNC; + lpDDHELCaps->dwZBufferBitDepths = d3dOptions->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + lpDDHELCaps->dwVidMemTotal = total9; + lpDDHELCaps->dwVidMemFree = free9; + lpDDHELCaps->dwNumFourCCCodes = ddrawCaps::NumberOfFOURCCCodes; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetDisplayMode(LPDDSURFACEDESC2 lpDDSurfaceDesc) { + Logger::debug("<<< DDraw7Interface::GetDisplayMode: Proxy"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + HRESULT hr = m_proxy->GetDisplayMode(lpDDSurfaceDesc); + if (unlikely(FAILED(hr))) + return hr; + + const D3DOptions* d3dOptions = m_commonIntf->GetOptions(); + + if (unlikely(d3dOptions->mask8BitModes && lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount == 8)) + lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount = 16; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetFourCCCodes(LPDWORD lpNumCodes, LPDWORD lpCodes) { + Logger::debug(">>> DDraw7Interface::GetFourCCCodes"); + + if (likely(lpNumCodes != nullptr && lpCodes != nullptr)) { + const uint32_t copyNumCodes = std::min(ddrawCaps::NumberOfFOURCCCodes, *lpNumCodes); + for (uint32_t i = 0; i < copyNumCodes; i++) { + lpCodes[i] = ddrawCaps::SupportedFourCCs[i]; + } + } + + if (lpNumCodes != nullptr) + *lpNumCodes = ddrawCaps::NumberOfFOURCCCodes; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetGDISurface(LPDIRECTDRAWSURFACE7 *lplpGDIDDSurface) { + Logger::debug("<<< DDraw7Interface::GetGDISurface: Proxy"); + + if(unlikely(lplpGDIDDSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpGDIDDSurface); + + Com gdiSurface; + HRESULT hr = m_proxy->GetGDISurface(&gdiSurface); + + if (unlikely(FAILED(hr))) { + Logger::debug("DDraw7Interface::GetGDISurface: Failed to retrieve GDI surface"); + return hr; + } + + if (unlikely(m_commonIntf->IsWrappedSurface(gdiSurface.ptr()))) { + *lplpGDIDDSurface = gdiSurface.ref(); + } else { + Logger::debug("DDraw7Interface::GetGDISurface: Received a non-wrapped GDI surface"); + try { + *lplpGDIDDSurface = ref(new DDraw7Surface(nullptr, std::move(gdiSurface), this, nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetMonitorFrequency(LPDWORD lpdwFrequency) { + Logger::debug("<<< DDraw7Interface::GetMonitorFrequency: Proxy"); + return m_proxy->GetMonitorFrequency(lpdwFrequency); + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetScanLine(LPDWORD lpdwScanLine) { + Logger::debug("<<< DDraw7Interface::GetScanLine: Proxy"); + return m_proxy->GetScanLine(lpdwScanLine); + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetVerticalBlankStatus(LPBOOL lpbIsInVB) { + Logger::debug("<<< DDraw7Interface::GetVerticalBlankStatus: Proxy"); + return m_proxy->GetVerticalBlankStatus(lpbIsInVB); + } + + // Should technically always return DDERR_ALREADYINITIALIZED, unless the + // interface is created via IClassFactory, however Requiem: Avenging Angel + // expects it to work on a regular interface too, after initially creating + // and releasing an interface through IClassFactory (but never initializing it). + // On native DDraw the initial interface most likely gets reused. In practice, + // applications that don't use IClassFactory won't call this, so keep it simple. + HRESULT STDMETHODCALLTYPE DDraw7Interface::Initialize(GUID* lpGUID) { + Logger::debug(">>> DDraw7Interface::Initialize"); + + if (unlikely(m_commonIntf->IsInitialized())) + return DDERR_ALREADYINITIALIZED; + + m_commonIntf->MarkAsInitialized(); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::RestoreDisplayMode() { + Logger::debug("<<< DDraw7Interface::RestoreDisplayMode: Proxy"); + return m_proxy->RestoreDisplayMode(); + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::SetCooperativeLevel(HWND hWnd, DWORD dwFlags) { + Logger::debug("<<< DDraw7Interface::SetCooperativeLevel: Proxy"); + + HRESULT hr = m_proxy->SetCooperativeLevel(hWnd, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + m_commonIntf->SetCooperativeLevel(hWnd, dwFlags); + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags) { + Logger::debug("<<< DDraw7Interface::SetDisplayMode: Proxy"); + + Logger::debug(str::format("DDraw7Interface::SetDisplayMode: ", dwWidth, "x", dwHeight, ":", dwBPP, "@", dwRefreshRate)); + + HRESULT hr = m_proxy->SetDisplayMode(dwWidth, dwHeight, dwBPP, dwRefreshRate, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + DDrawCommonSurface* ps = m_commonIntf->GetPrimarySurface(); + + if (likely(ps != nullptr)) { + hr = ps->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::warn("DDraw7Interface::SetDisplayMode: Failed to update primary surface desc"); + } + + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent && + m_commonIntf->GetOptions()->backBufferResize)) { + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Ignore any mode size dimensions when in windowed present mode + if (exclusiveMode) { + Logger::debug("DDraw7Interface::SetDisplayMode: Exclusive full-screen present mode in use"); + DDrawModeSize* modeSize = m_commonIntf->GetModeSize(); + if (modeSize->width != dwWidth || modeSize->height != dwHeight) { + modeSize->width = dwWidth; + modeSize->height = dwHeight; + } + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::WaitForVerticalBlank(DWORD dwFlags, HANDLE hEvent) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw7Interface::WaitForVerticalBlank: Proxy"); + m_proxy->WaitForVerticalBlank(dwFlags, hEvent); + } + + Logger::debug(">>> DDraw7Interface::WaitForVerticalBlank"); + + if (unlikely(dwFlags & DDWAITVB_BLOCKBEGINEVENT)) + return DDERR_UNSUPPORTED; + + // Switch to a default presentation interval when an application + // tries to wait for vertical blank, if we're not already doing so + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (unlikely(d3d9Device != nullptr && !m_commonIntf->GetWaitForVBlank())) { + Logger::info("DDraw7Interface::WaitForVerticalBlank: Switching to D3DPRESENT_INTERVAL_DEFAULT for presentation"); + + d3d9::D3DPRESENT_PARAMETERS resetParams = m_commonIntf->GetPresentParameters(); + resetParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + HRESULT hrReset = m_commonIntf->ResetD3D9Swapchain(&resetParams); + if (likely(SUCCEEDED(hrReset))) + m_commonIntf->SetWaitForVBlank(true); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetAvailableVidMem(LPDDSCAPS2 lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree) { + Logger::debug(">>> DDraw7Interface::GetAvailableVidMem"); + + if (unlikely(lpdwTotal == nullptr && lpdwFree == nullptr)) + return DD_OK; + + static constexpr DWORD Megabytes = 1024 * 1024; + static constexpr DWORD MaxMemory = ddrawCaps::MaxTextureMemory * Megabytes; + static constexpr DWORD ReservedMemory = ddrawCaps::ReservedTextureMemory * Megabytes; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (likely(d3d9Device != nullptr)) { + Logger::debug("DDraw7Interface::GetAvailableVidMem: Getting memory stats from D3D9"); + + DWORD total9 = static_cast(m_commonIntf->GetTotalTextureMemory()); + DWORD free9 = static_cast(d3d9Device->GetAvailableTextureMem()); + + if (likely(total9 >= MaxMemory)) { + const DWORD delta = total9 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free9 > delta + ReservedMemory ? free9 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw7Interface::GetAvailableVidMem: Total: ", total9)); + Logger::debug(str::format("DDraw7Interface::GetAvailableVidMem: Free : ", free9)); + + if (lpdwTotal != nullptr) + *lpdwTotal = total9; + if (lpdwFree != nullptr) + *lpdwFree = free9; + + } else { + Logger::debug("DDraw7Interface::GetAvailableVidMem: Getting memory stats from DDraw"); + + DWORD total7 = 0; + DWORD free7 = 0; + + HRESULT hr = m_proxy->GetAvailableVidMem(lpDDCaps, &total7, &free7); + if (unlikely(FAILED(hr))) { + Logger::err("DDraw7Interface::GetAvailableVidMem: Failed proxied call"); + if (lpdwTotal != nullptr) + *lpdwTotal = 0; + if (lpdwFree != nullptr) + *lpdwFree = 0; + return hr; + } + + Logger::debug(str::format("DDraw7Interface::GetAvailableVidMem: DDraw Total: ", total7)); + Logger::debug(str::format("DDraw7Interface::GetAvailableVidMem: DDraw Free : ", free7)); + + DWORD total9 = 0; + DWORD free9 = 0; + + if (unlikely(total7 < MaxMemory)) { + total9 = total7; + free9 = free7; + } else { + const DWORD delta = total7 - MaxMemory; + total9 = MaxMemory - ReservedMemory; + free9 = free7 > delta + ReservedMemory ? free7 - (delta + ReservedMemory) : 0; + } + + Logger::debug(str::format("DDraw7Interface::GetAvailableVidMem: Total: ", total9)); + Logger::debug(str::format("DDraw7Interface::GetAvailableVidMem: Free : ", free9)); + + if (lpdwTotal != nullptr) + *lpdwTotal = total9; + if (lpdwFree != nullptr) + *lpdwFree = free9; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetSurfaceFromDC(HDC hdc, LPDIRECTDRAWSURFACE7 *pSurf) { + Logger::debug(">>> DDraw7Interface::GetSurfaceFromDC"); + + if (unlikely(pSurf == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(pSurf); + + Com surface; + HRESULT hr = m_proxy->GetSurfaceFromDC(hdc, &surface); + + if (unlikely(FAILED(hr))) { + Logger::warn("DDraw7Interface::GetSurfaceFromDC: Failed to get surface from DC"); + return hr; + } + + try { + *pSurf = ref(new DDraw7Surface(nullptr, std::move(surface), this, nullptr, false)); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::RestoreAllSurfaces() { + Logger::debug("<<< DDraw7Interface::RestoreAllSurfaces: Proxy"); + return m_proxy->RestoreAllSurfaces(); + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::TestCooperativeLevel() { + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + + if (unlikely(d3d9Device == nullptr)) { + Logger::debug("<<< DDraw7Interface::TestCooperativeLevel: Proxy"); + return m_proxy->TestCooperativeLevel(); + } + + Logger::debug(">>> DDraw7Interface::TestCooperativeLevel"); + + HRESULT hr = d3d9Device->TestCooperativeLevel(); + if (unlikely(FAILED(hr))) + return DDERR_NOEXCLUSIVEMODE; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::GetDeviceIdentifier(LPDDDEVICEIDENTIFIER2 pDDDI, DWORD dwFlags) { + Logger::debug(">>> DDraw7Interface::GetDeviceIdentifier"); + + if (unlikely(pDDDI == nullptr)) + return DDERR_INVALIDPARAMS; + + // We need to share the device identifier with the underlying + // DDraw implementation, as some games validate it in various places + DDDEVICEIDENTIFIER2 pDDDIproxy = { }; + HRESULT hr = m_proxy->GetDeviceIdentifier(&pDDDIproxy, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + const d3d9::D3DADAPTER_IDENTIFIER9* adapterIdentifier9 = m_commonIntf->GetAdapterIdentifier(); + // This is typically the "2D accelerator" in the system + if (unlikely(dwFlags & DDGDI_GETHOSTIDENTIFIER)) { + Logger::debug("DDraw7Interface::GetDeviceIdentifier: Retrieving secondary adapter info"); + CopyToStringArray(pDDDI->szDriver, "vga.dll"); + CopyToStringArray(pDDDI->szDescription, "Standard VGA Adapter"); + pDDDI->liDriverVersion.QuadPart = 0; + pDDDI->dwVendorId = 0; + pDDDI->dwDeviceId = 0; + pDDDI->dwSubSysId = 0; + pDDDI->dwRevision = 0; + pDDDI->guidDeviceIdentifier = pDDDIproxy.guidDeviceIdentifier; + pDDDI->dwWHQLLevel = 0; + } else { + Logger::debug("DDraw7Interface::GetDeviceIdentifier: Retrieving primary adapter info"); + memcpy(&pDDDI->szDriver, &adapterIdentifier9->Driver, sizeof(adapterIdentifier9->Driver)); + memcpy(&pDDDI->szDescription, &adapterIdentifier9->Description, sizeof(adapterIdentifier9->Description)); + pDDDI->liDriverVersion.QuadPart = adapterIdentifier9->DriverVersion.QuadPart; + pDDDI->dwVendorId = adapterIdentifier9->VendorId; + pDDDI->dwDeviceId = adapterIdentifier9->DeviceId; + pDDDI->dwSubSysId = adapterIdentifier9->SubSysId; + pDDDI->dwRevision = adapterIdentifier9->Revision; + pDDDI->guidDeviceIdentifier = pDDDIproxy.guidDeviceIdentifier; + pDDDI->dwWHQLLevel = 1; + } + + Logger::info(str::format("DDraw7Interface::GetDeviceIdentifier: Reporting: ", pDDDI->szDescription)); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::StartModeTest(LPSIZE pModes, DWORD dwNumModes, DWORD dwFlags) { + Logger::warn("!!! DDraw7Interface::StartModeTest: Stub"); + + if (unlikely(!dwNumModes)) + return DD_OK; + + if (unlikely(pModes == nullptr)) + return DDERR_INVALIDPARAMS; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Interface::EvaluateMode(DWORD dwFlags, DWORD* pTimeout) { + Logger::warn("!!! DDraw7Interface::EvaluateMode: Stub"); + return DD_OK; + } + +} diff --git a/src/ddraw/ddraw7/ddraw7_interface.h b/src/ddraw/ddraw7/ddraw7_interface.h new file mode 100644 index 00000000000..ed659abbf95 --- /dev/null +++ b/src/ddraw/ddraw7/ddraw7_interface.h @@ -0,0 +1,100 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" +#include "../ddraw_caps.h" +#include "../ddraw_format.h" + +#include "../ddraw_common_interface.h" + +#include "../../d3d9/d3d9_bridge.h" + +namespace dxvk { + + class D3D7Interface; + class DDraw7Surface; + + /** + * \brief DirectDraw7 interface implementation + */ + class DDraw7Interface final : public DDrawWrappedObject { + + public: + DDraw7Interface( + DDrawCommonInterface* commonIntf, + Com&& proxyIntf); + + ~DDraw7Interface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE Compact(); + + HRESULT STDMETHODCALLTYPE CreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreatePalette(DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE CreateSurface(LPDDSURFACEDESC2 lpDDSurfaceDesc, LPDIRECTDRAWSURFACE7 *lplpDDSurface, IUnknown *pUnkOuter); + + HRESULT STDMETHODCALLTYPE DuplicateSurface(LPDIRECTDRAWSURFACE7 lpDDSurface, LPDIRECTDRAWSURFACE7 *lplpDupDDSurface); + + HRESULT STDMETHODCALLTYPE EnumDisplayModes(DWORD dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK2 lpEnumModesCallback); + + HRESULT STDMETHODCALLTYPE EnumSurfaces(DWORD dwFlags, LPDDSURFACEDESC2 lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE FlipToGDISurface(); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps); + + HRESULT STDMETHODCALLTYPE GetDisplayMode(LPDDSURFACEDESC2 lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE GetFourCCCodes(LPDWORD lpNumCodes, LPDWORD lpCodes); + + HRESULT STDMETHODCALLTYPE GetGDISurface(LPDIRECTDRAWSURFACE7 *lplpGDIDDSurface); + + HRESULT STDMETHODCALLTYPE GetMonitorFrequency(LPDWORD lpdwFrequency); + + HRESULT STDMETHODCALLTYPE GetScanLine(LPDWORD lpdwScanLine); + + HRESULT STDMETHODCALLTYPE GetVerticalBlankStatus(LPBOOL lpbIsInVB); + + HRESULT STDMETHODCALLTYPE Initialize(GUID* lpGUID); + + HRESULT STDMETHODCALLTYPE RestoreDisplayMode(); + + HRESULT STDMETHODCALLTYPE SetCooperativeLevel(HWND hWnd, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE WaitForVerticalBlank(DWORD dwFlags, HANDLE hEvent); + + HRESULT STDMETHODCALLTYPE GetAvailableVidMem(LPDDSCAPS2 lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree); + + HRESULT STDMETHODCALLTYPE GetSurfaceFromDC(HDC hdc, LPDIRECTDRAWSURFACE7 *pSurf); + + HRESULT STDMETHODCALLTYPE RestoreAllSurfaces(); + + HRESULT STDMETHODCALLTYPE TestCooperativeLevel(); + + HRESULT STDMETHODCALLTYPE GetDeviceIdentifier(LPDDDEVICEIDENTIFIER2 pDDDI, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE StartModeTest(LPSIZE pModes, DWORD dwNumModes, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE EvaluateMode(DWORD dwFlags, DWORD* pTimeout); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf.ptr(); + } + + private: + + static uint32_t s_intfCount; + uint32_t m_intfCount = 0; + + Com m_commonIntf; + + Com m_d3d7Intf; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw7/ddraw7_surface.cpp b/src/ddraw/ddraw7/ddraw7_surface.cpp new file mode 100644 index 00000000000..9b2d2e70dff --- /dev/null +++ b/src/ddraw/ddraw7/ddraw7_surface.cpp @@ -0,0 +1,1742 @@ +#include "ddraw7_surface.h" + +#include "../ddraw_gamma.h" +#include "../ddraw/ddraw_surface.h" +#include "../ddraw2/ddraw2_surface.h" +#include "../ddraw2/ddraw3_surface.h" +#include "../ddraw4/ddraw4_surface.h" +#include + +#include "../d3d3/d3d3_texture.h" +#include "../d3d6/d3d6_texture.h" + +namespace dxvk { + + uint32_t DDraw7Surface::s_surfCount = 0; + + DDraw7Surface::DDraw7Surface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDraw7Interface* pParent, + DDraw7Surface* pParentSurf, + bool isChildObject) + : DDrawWrappedObject(pParent, std::move(surfProxy), nullptr) + , m_isChildObject ( isChildObject ) + , m_commonSurf ( commonSurf ) + , m_parentSurf ( pParentSurf ) { + if (m_parent != nullptr) { + m_commonIntf = m_parent->GetCommonInterface(); + } else if (m_parentSurf != nullptr) { + m_commonIntf = m_parentSurf->GetCommonInterface(); + } else if (m_commonSurf != nullptr) { + m_commonIntf = m_commonSurf->GetCommonInterface(); + } else { + throw DxvkError("DDraw7Surface: ERROR! Failed to retrieve the common interface!"); + } + + if (m_commonSurf == nullptr) + m_commonSurf = new DDrawCommonSurface(m_commonIntf); + + // Retrieve and cache the proxy surface desc + if (!m_commonSurf->IsDesc2Set()) { + DDSURFACEDESC2 desc2; + desc2.dwSize = sizeof(DDSURFACEDESC2); + HRESULT hr = m_proxy->GetSurfaceDesc(&desc2); + + if (unlikely(FAILED(hr))) { + throw DxvkError("DDraw7Surface: ERROR! Failed to retrieve new surface desc!"); + } else { + m_commonSurf->SetDesc2(desc2); + } + } + + m_commonIntf->AddWrappedSurface(this); + + m_commonSurf->SetDD7Surface(this); + + if (m_parentSurf != nullptr && !m_commonSurf->IsFrontBuffer() + && m_parentSurf->GetCommonSurface()->IsBackBufferOrFlippable() + && !m_commonIntf->GetOptions()->forceSingleBackBuffer) { + const uint32_t index = m_parentSurf->GetCommonSurface()->GetBackBufferIndex(); + m_commonSurf->IncrementBackBufferIndex(index); + } + + if (m_parent != nullptr && m_isChildObject) + m_parent->AddRef(); + + // Cube map face surfaces + m_cubeMapSurfaces.fill(nullptr); + + m_surfCount = ++s_surfCount; + + Logger::debug(str::format("DDraw7Surface: Created a new surface nr. [[7-", m_surfCount, "]]")); + + if (m_commonSurf->GetOrigin() == nullptr) { + m_commonSurf->SetOrigin(this); + m_commonSurf->SetIsAttached(m_parentSurf != nullptr); + m_commonSurf->ListSurfaceDetails(); + } + } + + DDraw7Surface::~DDraw7Surface() { + if (m_commonSurf->GetOrigin() == this) + m_commonSurf->SetOrigin(nullptr); + + // Clear the cached depth stencil on the parent if matched + if (unlikely(m_parentSurf != nullptr && m_commonSurf->IsDepthStencil() + && m_parentSurf->GetAttachedDepthStencil() == this)) { + m_parentSurf->ClearAttachedDepthStencil(); + } + + m_commonIntf->RemoveWrappedSurface(this); + + // Release all public references on all attached surfaces + for (auto & attachedSurface : m_attachedSurfaces) { + uint32_t attachedRef; + do { + attachedRef = attachedSurface.second->Release(); + } while (attachedRef > 0); + } + + if (m_parent != nullptr && m_isChildObject) + m_parent->Release(); + + m_commonSurf->SetDD7Surface(nullptr); + + Logger::debug(str::format("DDraw7Surface: Surface nr. [[7-", m_surfCount, "]] bites the dust")); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDraw7Surface::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + // Black & White queries for IDirect3DTexture2 for whatever reason... + if (unlikely(riid == __uuidof(IDirect3DTexture2))) { + Logger::debug("DDraw7Surface::QueryInterface: Query for IDirect3DTexture2"); + return E_NOINTERFACE; + } + // Wrap IDirectDrawGammaControl, to potentially ignore application set gamma ramps + if (riid == __uuidof(IDirectDrawGammaControl)) { + Logger::debug("DDraw7Surface::QueryInterface: Query for IDirectDrawGammaControl"); + void* gammaControlProxiedVoid = nullptr; + // This can never reasonably fail + m_proxy->QueryInterface(__uuidof(IDirectDrawGammaControl), &gammaControlProxiedVoid); + Com gammaControlProxied = static_cast(gammaControlProxiedVoid); + *ppvObject = ref(new DDrawGammaControl(m_commonSurf.ptr(), std::move(gammaControlProxied), this)); + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawColorControl))) { + Logger::debug("DDraw7Surface::QueryInterface: Query for IDirectDrawColorControl"); + return E_NOINTERFACE; + } + // Some games query for legacy ddraw surfaces + if (unlikely(riid == __uuidof(IUnknown) + || riid == __uuidof(IDirectDrawSurface))) { + if (m_commonSurf->GetDDSurface() != nullptr) { + Logger::debug("DDraw7Surface::QueryInterface: Query for existing IDirectDrawSurface"); + return m_commonSurf->GetDDSurface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw7Surface::QueryInterface: Query for legacy IDirectDrawSurface"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDrawSurface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDDInterface(), nullptr, false)); + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface2))) { + if (m_commonSurf->GetDD2Surface() != nullptr) { + Logger::debug("DDraw7Surface::QueryInterface: Query for existing IDirectDrawSurface2"); + return m_commonSurf->GetDD2Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw7Surface::QueryInterface: Query for legacy IDirectDrawSurface2"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw2Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonSurf->GetDDSurface(), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface3))) { + if (m_commonSurf->GetDD3Surface() != nullptr) { + Logger::debug("DDraw7Surface::QueryInterface: Query for existing IDirectDrawSurface3"); + return m_commonSurf->GetDD3Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw7Surface::QueryInterface: Query for legacy IDirectDrawSurface3"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw3Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonSurf->GetDDSurface(), nullptr)); + + return S_OK; + } + if (unlikely(riid == __uuidof(IDirectDrawSurface4))) { + if (m_commonSurf->GetDD4Surface() != nullptr) { + Logger::debug("DDraw7Surface::QueryInterface: Query for existing IDirectDrawSurface4"); + return m_commonSurf->GetDD4Surface()->QueryInterface(riid, ppvObject); + } + + Logger::debug("DDraw7Surface::QueryInterface: Query for legacy IDirectDrawSurface4"); + + Com ppvProxyObject; + HRESULT hr = m_proxy->QueryInterface(riid, reinterpret_cast(&ppvProxyObject)); + if (unlikely(FAILED(hr))) + return hr; + + *ppvObject = ref(new DDraw4Surface(m_commonSurf.ptr(), std::move(ppvProxyObject), m_commonIntf->GetDD4Interface(), nullptr, false)); + + return S_OK; + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + // On IDirectDrawSurface7, this call will only attach DDSCAPS_ZBUFFER + // type surfaces and will fail if called with any other surface type. + HRESULT STDMETHODCALLTYPE DDraw7Surface::AddAttachedSurface(LPDIRECTDRAWSURFACE7 lpDDSAttachedSurface) { + Logger::debug("<<< DDraw7Surface::AddAttachedSurface: Proxy"); + + if (unlikely(lpDDSAttachedSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + Logger::warn("DDraw7Surface::AddAttachedSurface: Received an unwrapped surface"); + return DDERR_CANNOTATTACHSURFACE; + } + + DDraw7Surface* ddraw7Surface = static_cast(lpDDSAttachedSurface); + + HRESULT hr = m_proxy->AddAttachedSurface(ddraw7Surface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + ddraw7Surface->SetParentSurface(this); + m_depthStencil = ddraw7Surface; + + return hr; + } + + // Docs: "The IDirectDrawSurface7::AddOverlayDirtyRect method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw7Surface::AddOverlayDirtyRect(LPRECT lpRect) { + Logger::debug(">>> DDraw7Surface::AddOverlayDirtyRect"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) { + Logger::debug("<<< DDraw7Surface::Blt: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDraw7Surface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw7Surface::Blt: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw7Surface::Blt: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw7Surface::Blt: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw7Surface::Blt: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + // Forward DDBLT_DEPTHFILL clears to D3D9 if done on the current depth stencil + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_DEPTHFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9DepthStencil(m_d3d9.ptr()))) { + Logger::debug("DDraw7Surface::Blt: Clearing d3d9 depth stencil"); + + HRESULT hrClear; + const float zClear = m_commonSurf->GetNormalizedFloatDepth(lpDDBltFx->dwFillDepth); + + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0, zClear, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_ZBUFFER, 0, zClear, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDraw7Surface::Blt: Failed to clear d3d9 depth"); + } + // Forward DDBLT_COLORFILL clears to D3D9 if done on the current render target + if (unlikely(lpDDSrcSurface == nullptr && + (dwFlags & DDBLT_COLORFILL) && + lpDDBltFx != nullptr && + m_commonIntf->IsCurrentD3D9RenderTarget(m_d3d9.ptr()))) { + Logger::debug("DDraw7Surface::Blt: Clearing d3d9 render target"); + + HRESULT hrClear; + if (lpDestRect == nullptr) { + hrClear = m_d3d9Device->Clear(0, NULL, D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } else { + hrClear = m_d3d9Device->Clear(1, reinterpret_cast(lpDestRect), D3DCLEAR_TARGET, lpDDBltFx->dwFillColor, 0.0f, 0); + } + if (unlikely(FAILED(hrClear))) + Logger::warn("DDraw7Surface::Blt: Failed to clear d3d9 render target"); + } + + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDraw7Surface::Blt: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->Blt(lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFx); + } else { + DDraw7Surface* ddraw7Surface = static_cast(lpDDSrcSurface); + hr = m_proxy->Blt(lpDestRect, ddraw7Surface->GetProxied(), lpSrcRect, dwFlags, lpDDBltFx); + } + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (!m_commonSurf->IsTextureOrCubeMap()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw7Surface::Blt: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + // Docs: "The IDirectDrawSurface7::BltBatch method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw7Surface::BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) { + Logger::debug(">>> DDraw7Surface::BltBatch"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) { + Logger::debug("<<< DDraw7Surface::BltFast: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (likely(lpDDSrcSurface != nullptr && m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + DDraw7Surface* sourceSurface = static_cast(lpDDSrcSurface); + if (unlikely(sourceSurface->GetCommonSurface()->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw7Surface::BltFast: Source surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !sourceSurface->IsInitialized())) + sourceSurface->InitializeOrUploadD3D9(); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw7Surface::BltFast: Source surface is a swapchain surface"); + } + } else if (unlikely(sourceSurface->GetCommonSurface()->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw7Surface::BltFast: Source surface is a depth stencil"); + + if (likely(sourceSurface->IsInitialized())) + BlitToDDrawSurface(sourceSurface->GetProxied(), sourceSurface->GetD3D9()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw7Surface::BltFast: Source surface is a depth stencil"); + } + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + const bool exclusiveMode = (m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE) + && !m_commonIntf->GetOptions()->ignoreExclusiveMode; + + // Eclusive mode back buffer guard + if (exclusiveMode && m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface() && + m_commonIntf->GetOptions()->backBufferGuard != D3DBackBufferGuard::Disabled) { + return DD_OK; + // Windowed mode presentation path + } else if (!exclusiveMode && m_commonIntf->HasDrawn() && m_commonSurf->IsPrimarySurface()) { + m_commonIntf->ResetDrawTracking(); + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + return DD_OK; + } + } + + HRESULT hr; + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSrcSurface))) { + if (unlikely(lpDDSrcSurface != nullptr)) { + Logger::warn("DDraw7Surface::BltFast: Received an unwrapped source surface"); + return DDERR_GENERIC; + } + hr = m_proxy->BltFast(dwX, dwY, lpDDSrcSurface, lpSrcRect, dwTrans); + } else { + DDraw7Surface* ddraw7Surface = static_cast(lpDDSrcSurface); + hr = m_proxy->BltFast(dwX, dwY, ddraw7Surface->GetProxied(), lpSrcRect, dwTrans); + } + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (!m_commonSurf->IsTextureOrCubeMap()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw7Surface::BltFast: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + // This call will only detach DDSCAPS_ZBUFFER type surfaces and will reject anything else. + HRESULT STDMETHODCALLTYPE DDraw7Surface::DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE7 lpDDSAttachedSurface) { + Logger::debug("<<< DDraw7Surface::DeleteAttachedSurface: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSAttachedSurface))) { + if (unlikely(lpDDSAttachedSurface != nullptr)) { + Logger::warn("DDraw7Surface::DeleteAttachedSurface: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + HRESULT hrProxy = m_proxy->DeleteAttachedSurface(dwFlags, lpDDSAttachedSurface); + + // If lpDDSAttachedSurface is NULL, then all surfaces are detached + if (lpDDSAttachedSurface == nullptr && likely(SUCCEEDED(hrProxy))) + m_depthStencil = nullptr; + + return hrProxy; + } + + DDraw7Surface* ddraw7Surface = static_cast(lpDDSAttachedSurface); + + HRESULT hr = m_proxy->DeleteAttachedSurface(dwFlags, ddraw7Surface->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + if (likely(m_depthStencil == ddraw7Surface)) { + ddraw7Surface->ClearParentSurface(); + m_depthStencil = nullptr; + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpEnumSurfacesCallback) { + Logger::debug(">>> DDraw7Surface::EnumAttachedSurfaces"); + + if (unlikely(lpEnumSurfacesCallback == nullptr)) + return DDERR_INVALIDPARAMS; + + std::vector attachedSurfaces; + // Enumerate all attached surfaces from the underlying DDraw implementation + m_proxy->EnumAttachedSurfaces(reinterpret_cast(&attachedSurfaces), EnumAttachedSurfaces7Callback); + + HRESULT hr = DDENUMRET_OK; + + // Wrap surfaces as needed and perform the actual callback the application is requesting + auto surfaceIt = attachedSurfaces.begin(); + while (surfaceIt != attachedSurfaces.end() && hr == DDENUMRET_OK) { + Com surface7 = surfaceIt->surface7; + + auto attachedSurfaceIter = m_attachedSurfaces.find(surface7.ptr()); + if (unlikely(attachedSurfaceIter == m_attachedSurfaces.end())) { + // Return the already attached depth surface if it exists + if (unlikely(m_depthStencil != nullptr && surface7.ptr() == m_depthStencil->GetProxied())) { + hr = lpEnumSurfacesCallback(m_depthStencil.ref(), &surfaceIt->desc2, lpContext); + } else { + Com ddraw7Surface = new DDraw7Surface(nullptr, std::move(surface7), m_parent, this, false); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(ddraw7Surface->GetProxied()), + std::forward_as_tuple(ddraw7Surface.ref())); + hr = lpEnumSurfacesCallback(ddraw7Surface.ref(), &surfaceIt->desc2, lpContext); + } + } else { + hr = lpEnumSurfacesCallback(attachedSurfaceIter->second.ref(), &surfaceIt->desc2, lpContext); + } + + ++surfaceIt; + } + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpfnCallback) { + Logger::debug("<<< DDraw7Surface::EnumOverlayZOrders: Proxy"); + return m_proxy->EnumOverlayZOrders(dwFlags, lpContext, lpfnCallback); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::Flip(LPDIRECTDRAWSURFACE7 lpDDSurfaceTargetOverride, DWORD dwFlags) { + Logger::debug("*** DDraw7Surface::Flip: Presenting"); + + // Lost surfaces are not flippable + HRESULT hr = m_proxy->IsLost(); + if (unlikely(FAILED(hr))) { + Logger::debug("DDraw7Surface::Flip: Lost surface"); + return hr; + } + + if (unlikely(!(m_commonSurf->IsFrontBuffer() || m_commonSurf->IsBackBufferOrFlippable()))) { + Logger::debug("DDraw7Surface::Flip: Unflippable surface"); + return DDERR_NOTFLIPPABLE; + } + + const bool exclusiveMode = m_commonIntf->GetCooperativeLevel() & DDSCL_EXCLUSIVE; + + // Non-exclusive mode validations + if (unlikely(m_commonSurf->IsPrimarySurface() && !exclusiveMode)) { + Logger::debug("DDraw7Surface::Flip: Primary surface flip in non-exclusive mode"); + return DDERR_NOEXCLUSIVEMODE; + } + + // Exclusive mode validations + if (unlikely(m_commonSurf->IsBackBufferOrFlippable() && exclusiveMode)) { + Logger::debug("DDraw7Surface::Flip: Back buffer flip in exclusive mode"); + return DDERR_NOTFLIPPABLE; + } + + Com surf7; + if (m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride)) { + surf7 = static_cast(lpDDSurfaceTargetOverride); + + if (unlikely(!surf7->GetCommonSurface()->IsBackBufferOrFlippable())) { + Logger::debug("DDraw7Surface::Flip: Unflippable override surface"); + return DDERR_NOTFLIPPABLE; + } + } + + RefreshD3D9Device(); + if (likely(m_d3d9Device != nullptr)) { + Logger::debug("*** DDraw7Surface::Flip: Presenting"); + + m_commonIntf->ResetDrawTracking(); + + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(!IsInitialized())) + InitializeD3D9(m_commonIntf->IsCurrentRenderTarget(this)); + + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride))) { + if (unlikely(lpDDSurfaceTargetOverride != nullptr)) { + Logger::warn("DDraw7Surface::Flip: Received an unwrapped surface"); + return DDERR_GENERIC; + } + if (likely(m_commonIntf->IsCurrentRenderTarget(this))) + m_commonIntf->SetFlipRTSurfaceAndFlags(lpDDSurfaceTargetOverride, dwFlags); + return m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } else { + if (likely(m_commonIntf->IsCurrentRenderTarget(this))) + m_commonIntf->SetFlipRTSurfaceAndFlags(lpDDSurfaceTargetOverride, dwFlags); + return m_proxy->Flip(surf7->GetProxied(), dwFlags); + } + } + + // If the interface is waiting for VBlank and we get a no VSync flip, switch + // to doing immediate presents by resetting the swapchain appropriately + if (unlikely(m_commonIntf->GetWaitForVBlank() && (dwFlags & DDFLIP_NOVSYNC))) { + Logger::info("DDraw7Surface::Flip: Switching to D3DPRESENT_INTERVAL_IMMEDIATE for presentation"); + + d3d9::D3DPRESENT_PARAMETERS resetParams = m_commonIntf->GetPresentParameters(); + resetParams.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + HRESULT hrReset = m_commonIntf->ResetD3D9Swapchain(&resetParams); + if (unlikely(FAILED(hrReset))) { + Logger::warn("DDraw7Surface::Flip: Failed D3D9 swapchain reset"); + } else { + m_commonIntf->SetWaitForVBlank(false); + } + // If the interface is not waiting for VBlank and we stop getting DDFLIP_NOVSYNC + // flags, reset the swapchain in order to return to VSync presentation using DEFAULT + } else if (unlikely(!m_commonIntf->GetWaitForVBlank() && IsVSyncFlipFlag(dwFlags))) { + Logger::info("DDraw7Surface::Flip: Switching to D3DPRESENT_INTERVAL_DEFAULT for presentation"); + + d3d9::D3DPRESENT_PARAMETERS resetParams = m_commonIntf->GetPresentParameters(); + resetParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + HRESULT hrReset = m_commonIntf->ResetD3D9Swapchain(&resetParams); + if (unlikely(FAILED(hrReset))) { + Logger::warn("DDraw7Surface::Flip: Failed D3D9 swapchain reset"); + } else { + m_commonIntf->SetWaitForVBlank(true); + } + } + + m_d3d9Device->Present(NULL, NULL, NULL, NULL); + // If we don't yet have a device, perhaps a D3D7 application is trying to + // present exclusively on DDraw (such as a main menu), so allow the flip + } else { + Logger::debug("<<< DDraw7Surface::Flip: Proxy"); + + // Update the VBlank wait status based on the flip flags + m_commonIntf->SetWaitForVBlank(IsVSyncFlipFlag(dwFlags)); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSurfaceTargetOverride))) { + m_proxy->Flip(lpDDSurfaceTargetOverride, dwFlags); + } else { + m_proxy->Flip(surf7->GetProxied(), dwFlags); + } + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetAttachedSurface(LPDDSCAPS2 lpDDSCaps, LPDIRECTDRAWSURFACE7 *lplpDDAttachedSurface) { + Logger::debug("<<< DDraw7Surface::GetAttachedSurface: Proxy"); + + if (unlikely(lpDDSCaps == nullptr || lplpDDAttachedSurface == nullptr)) + return DDERR_INVALIDPARAMS; + + if (lpDDSCaps->dwCaps & DDSCAPS_PRIMARYSURFACE) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for the primary surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FRONTBUFFER) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for the front buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_BACKBUFFER) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for the back buffer"); + else if (lpDDSCaps->dwCaps & DDSCAPS_FLIP) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for a flippable surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OFFSCREENPLAIN) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for an offscreen plain surface"); + else if (lpDDSCaps->dwCaps & DDSCAPS_ZBUFFER) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for a depth stencil"); + else if ((lpDDSCaps->dwCaps & DDSCAPS_MIPMAP) + || (lpDDSCaps->dwCaps2 & DDSCAPS2_MIPMAPSUBLEVEL)) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for a texture mip map"); + else if (lpDDSCaps->dwCaps & DDSCAPS_TEXTURE) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for a texture"); + else if (lpDDSCaps->dwCaps2 & DDSCAPS2_CUBEMAP) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for a cube map"); + else if (lpDDSCaps->dwCaps & DDSCAPS_OVERLAY) + Logger::debug("DDraw7Surface::GetAttachedSurface: Querying for an overlay"); + + Com surface; + HRESULT hr = m_proxy->GetAttachedSurface(lpDDSCaps, &surface); + + // These are rather common, as some games query expecting to get nothing in return, for + // example it's a common use case to query the mip attach chain until nothing is returned + if (FAILED(hr)) { + Logger::debug("DDraw7Surface::GetAttachedSurface: Failed to find the requested surface"); + *lplpDDAttachedSurface = surface.ptr(); + return hr; + } + + try { + auto attachedSurfaceIter = m_attachedSurfaces.find(surface.ptr()); + if (unlikely(attachedSurfaceIter == m_attachedSurfaces.end())) { + // Return the already attached depth surface if it exists + if (unlikely(m_depthStencil != nullptr && surface.ptr() == m_depthStencil->GetProxied())) { + *lplpDDAttachedSurface = m_depthStencil.ref(); + } else { + Com ddraw7Surface = new DDraw7Surface(nullptr, std::move(surface), m_parent, this, false); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(ddraw7Surface->GetProxied()), + std::forward_as_tuple(ddraw7Surface.ref())); + *lplpDDAttachedSurface = ddraw7Surface.ref(); + } + } else { + *lplpDDAttachedSurface = attachedSurfaceIter->second.ref(); + } + } catch (const DxvkError& e) { + Logger::err(e.message()); + *lplpDDAttachedSurface = nullptr; + return DDERR_GENERIC; + } + + return DD_OK; + } + + // Blitting can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetBltStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw7Surface::GetBltStatus: Proxy"); + m_proxy->GetBltStatus(dwFlags); + } + + Logger::debug(">>> DDraw7Surface::GetBltStatus"); + + if (likely(dwFlags == DDGBS_CANBLT || dwFlags == DDGBS_ISBLTDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetCaps(LPDDSCAPS2 lpDDSCaps) { + Logger::debug(">>> DDraw7Surface::GetCaps"); + + if (unlikely(lpDDSCaps == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDSCaps = m_commonSurf->GetDesc2()->ddsCaps; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper) { + Logger::debug(">>> DDraw7Surface::GetClipper"); + + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + DDrawClipper* clipper = m_commonSurf->GetClipper(); + + if (unlikely(clipper == nullptr)) + return DDERR_NOCLIPPERATTACHED; + + *lplpDDClipper = ref(clipper); + + return D3D_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDraw7Surface::GetColorKey: Proxy"); + return m_proxy->GetColorKey(dwFlags, lpDDColorKey); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetDC(HDC *lphDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + if (unlikely(lphDC == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lphDC); + + // Foward GetDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDraw7Surface::GetDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw7Surface::GetDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_d3d9->GetDC(lphDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDraw7Surface::GetDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDraw7Surface::GetDC: Proxy"); + return m_proxy->GetDC(lphDC); + } + + // Flipping can be done at any time and completes within its call frame + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetFlipStatus(DWORD dwFlags) { + if (unlikely(m_commonIntf->GetOptions()->forceProxiedPresent)) { + Logger::debug("<<< DDraw7Surface::GetFlipStatus: Proxy"); + m_proxy->GetFlipStatus(dwFlags); + } + + Logger::debug(">>> DDraw7Surface::GetFlipStatus"); + + if (likely(dwFlags == DDGFS_CANFLIP || dwFlags == DDGFS_ISFLIPDONE)) + return DD_OK; + + return DDERR_INVALIDPARAMS; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetOverlayPosition(LPLONG lplX, LPLONG lplY) { + Logger::debug("<<< DDraw7Surface::GetOverlayPosition: Proxy"); + return m_proxy->GetOverlayPosition(lplX, lplY); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette) { + Logger::debug(">>> DDraw7Surface::GetPalette"); + + if (unlikely(lplpDDPalette == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDPalette); + + DDrawPalette* palette = m_commonSurf->GetPalette(); + + if (unlikely(palette == nullptr)) + return DDERR_NOPALETTEATTACHED; + + *lplpDDPalette = ref(palette); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat) { + Logger::debug(">>> DDraw7Surface::GetPixelFormat"); + + if (unlikely(lpDDPixelFormat == nullptr)) + return DDERR_INVALIDPARAMS; + + *lpDDPixelFormat = m_commonSurf->GetDesc2()->ddpfPixelFormat; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetSurfaceDesc(LPDDSURFACEDESC2 lpDDSurfaceDesc) { + Logger::debug(">>> DDraw7Surface::GetSurfaceDesc"); + + if (unlikely(lpDDSurfaceDesc == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(lpDDSurfaceDesc->dwSize != sizeof(DDSURFACEDESC2))) + return DDERR_INVALIDPARAMS; + + *lpDDSurfaceDesc = *m_commonSurf->GetDesc2(); + + return DD_OK; + } + + // According to the docs: "Because the DirectDrawSurface object is initialized + // when it's created, this method always returns DDERR_ALREADYINITIALIZED." + HRESULT STDMETHODCALLTYPE DDraw7Surface::Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC2 lpDDSurfaceDesc) { + Logger::debug(">>> DDraw7Surface::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::IsLost() { + Logger::debug("<<< DDraw7Surface::IsLost: Proxy"); + return m_proxy->IsLost(); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::Lock(LPRECT lpDestRect, LPDDSURFACEDESC2 lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) { + Logger::debug("<<< DDraw7Surface::Lock: Proxy"); + + // Write back any flippable surfaces or depth stencils from D3D9 + if (unlikely(m_commonSurf->IsGuardableSurface())) { + if (m_commonIntf->GetOptions()->backBufferWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw7Surface::Lock: Surface is a swapchain surface"); + + if (unlikely(m_commonIntf->GetOptions()->apitraceMode && !IsInitialized())) + InitializeOrUploadD3D9(); + + if (likely(IsInitialized())) + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + } else { + static bool s_swapchainWarningShown; + + if (!std::exchange(s_swapchainWarningShown, true)) + Logger::warn("DDraw7Surface::Lock: Surface is a swapchain surface"); + } + } else if (unlikely(m_commonSurf->IsDepthStencil())) { + if (m_commonIntf->GetOptions()->depthWriteBack || m_commonIntf->GetOptions()->apitraceMode) { + Logger::debug("DDraw7Surface::Lock: Surface is a depth stencil"); + + if (likely(IsInitialized())) + BlitToDDrawSurface(m_proxy.ptr(), m_d3d9.ptr()); + } else { + static bool s_depthStencilWarningShown; + + if (!std::exchange(s_depthStencilWarningShown, true)) + Logger::warn("DDraw7Surface::Lock: Surface is a depth stencil"); + } + } + + return m_proxy->Lock(lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::ReleaseDC(HDC hDC) { + if (likely(!m_commonIntf->GetOptions()->forceProxiedPresent)) { + // Foward ReleaseDC calls if we have drawn and the surface is flippable + RefreshD3D9Device(); + if (m_d3d9Device != nullptr && (m_commonIntf->HasDrawn() && + m_commonSurf->IsGuardableSurface())) { + Logger::debug(">>> DDraw7Surface::ReleaseDC"); + + if (unlikely(!IsInitialized())) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw7Surface::ReleaseDC: Failed to initialize d3d9 surface"); + } + + HRESULT hr9 = m_d3d9->ReleaseDC(hDC); + if (unlikely(FAILED(hr9))) + Logger::warn("DDraw7Surface::ReleaseDC: Failed D3D9 call"); + return hr9; + } + } + + Logger::debug("<<< DDraw7Surface::ReleaseDC: Proxy"); + + HRESULT hr = m_proxy->ReleaseDC(hDC); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (m_commonSurf->IsTexture()) { + m_commonSurf->DirtyMipMaps(); + } else if (unlikely(m_commonIntf->GetOptions()->apitraceMode)) { + // We should ideally upload the surface contents here at all times, + // however some games are amazing, and do hundreds of locks on the same + // surface per frame, so this would absolutely tank performance + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw7Surface::ReleaseDC: Failed upload to d3d9 surface"); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::Restore() { + Logger::debug("<<< DDraw7Surface::Restore: Proxy"); + return m_proxy->Restore(); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper) { + Logger::debug("<<< DDraw7Surface::SetClipper: Proxy"); + + // A nullptr lpDDClipper gets the current clipper detached + if (lpDDClipper == nullptr) { + HRESULT hr = m_proxy->SetClipper(lpDDClipper); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(nullptr); + } else { + DDrawClipper* ddrawClipper = static_cast(lpDDClipper); + + HRESULT hr = m_proxy->SetClipper(ddrawClipper->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetClipper(ddrawClipper); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) { + Logger::debug("<<< DDraw7Surface::SetColorKey: Proxy"); + + HRESULT hr = m_proxy->SetColorKey(dwFlags, lpDDColorKey); + if (unlikely(FAILED(hr))) + return hr; + + hr = m_commonSurf->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::err("DDraw7Surface::SetColorKey: Failed to retrieve updated surface desc"); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::SetOverlayPosition(LONG lX, LONG lY) { + Logger::debug("<<< DDraw7Surface::SetOverlayPosition: Proxy"); + return m_proxy->SetOverlayPosition(lX, lY); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) { + Logger::debug("<<< DDraw7Surface::SetPalette: Proxy"); + + // A nullptr lpDDPalette gets the current palette detached + if (lpDDPalette == nullptr) { + HRESULT hr = m_proxy->SetPalette(lpDDPalette); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(nullptr); + } else { + DDrawPalette* ddrawPalette = static_cast(lpDDPalette); + + HRESULT hr = m_proxy->SetPalette(ddrawPalette->GetProxied()); + if (unlikely(FAILED(hr))) + return hr; + + m_commonSurf->SetPalette(ddrawPalette); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::Unlock(LPRECT lpSurfaceData) { + Logger::debug("<<< DDraw7Surface::Unlock: Proxy"); + + HRESULT hr = m_proxy->Unlock(lpSurfaceData); + + if (likely(SUCCEEDED(hr))) { + // Textures and cubemaps get uploaded during SetTexture calls + if (!m_commonSurf->IsTextureOrCubeMap()) { + HRESULT hrUpload = InitializeOrUploadD3D9(); + if (unlikely(FAILED(hrUpload))) + Logger::warn("DDraw7Surface::Unlock: Failed upload to d3d9 surface"); + } else { + m_commonSurf->DirtyMipMaps(); + } + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE7 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) { + Logger::debug("<<< DDraw7Surface::UpdateOverlay: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDDestSurface))) { + Logger::warn("DDraw7Surface::UpdateOverlay: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw7Surface* ddraw7Surface = static_cast(lpDDDestSurface); + return m_proxy->UpdateOverlay(lpSrcRect, ddraw7Surface->GetProxied(), lpDestRect, dwFlags, lpDDOverlayFx); + } + + // Docs: "The IDirectDrawSurface7::UpdateOverlayDisplay method is not currently implemented." + HRESULT STDMETHODCALLTYPE DDraw7Surface::UpdateOverlayDisplay(DWORD dwFlags) { + Logger::debug(">>> DDraw7Surface::UpdateOverlayDisplay"); + return DDERR_UNSUPPORTED; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE7 lpDDSReference) { + Logger::debug("<<< DDraw7Surface::UpdateOverlayZOrder: Proxy"); + + if (unlikely(!m_commonIntf->IsWrappedSurface(lpDDSReference))) { + Logger::warn("DDraw7Surface::UpdateOverlayZOrder: Received an unwrapped surface"); + return DDERR_GENERIC; + } + + DDraw7Surface* ddraw7Surface = static_cast(lpDDSReference); + return m_proxy->UpdateOverlayZOrder(dwFlags, ddraw7Surface->GetProxied()); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetDDInterface(LPVOID *lplpDD) { + Logger::debug(">>> DDraw7Surface::GetDDInterface"); + + if (unlikely(lplpDD == nullptr)) + return DDERR_INVALIDPARAMS; + + // Was an easy footgun to return a proxied interface + *lplpDD = ref(m_parent); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::PageLock(DWORD dwFlags) { + Logger::debug("<<< DDraw7Surface::PageLock: Proxy"); + return m_proxy->PageLock(dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::PageUnlock(DWORD dwFlags) { + Logger::debug("<<< DDraw7Surface::PageUnlock: Proxy"); + return m_proxy->PageUnlock(dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::SetSurfaceDesc(LPDDSURFACEDESC2 lpDDSD, DWORD dwFlags) { + Logger::debug("<<< DDraw7Surface::SetSurfaceDesc: Proxy"); + + // Can be used only to set the surface data and pixel format + // used by an explicit system-memory surface (will be validated) + HRESULT hr = m_proxy->SetSurfaceDesc(lpDDSD, dwFlags); + if (unlikely(FAILED(hr))) + return hr; + + hr = m_commonSurf->RefreshSurfaceDescripton(); + if (unlikely(FAILED(hr))) + Logger::err("DDraw4Surface::SetSurfaceDesc: Failed to retrieve updated surface desc"); + + // We may need to recreate the d3d9 object based on the new desc + m_d3d9 = nullptr; + + if (!m_commonSurf->IsTextureOrCubeMap()) { + InitializeOrUploadD3D9(); + } else { + m_commonSurf->DirtyMipMaps(); + } + + return hr; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::SetPrivateData(REFGUID tag, LPVOID pData, DWORD cbSize, DWORD dwFlags) { + Logger::debug("<<< DDraw7Surface::SetPrivateData: Proxy"); + return m_proxy->SetPrivateData(tag, pData, cbSize, dwFlags); + } + + // Silent Hunter II needs to get these from the proxy surface, as it + // sets them otherwise... it doesn't call SetPrivateData at all + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetPrivateData(REFGUID tag, LPVOID pBuffer, LPDWORD pcbBufferSize) { + Logger::debug("<<< DDraw7Surface::GetPrivateData: Proxy"); + return m_proxy->GetPrivateData(tag, pBuffer, pcbBufferSize); + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::FreePrivateData(REFGUID tag) { + Logger::debug("<<< DDraw7Surface::FreePrivateData: Proxy"); + return m_proxy->FreePrivateData(tag); + } + + // Docs: "The only defined uniqueness value is 0, indicating that the surface + // is likely to be changing beyond the control of DirectDraw." + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetUniquenessValue(LPDWORD pValue) { + Logger::debug(">>> DDraw7Surface::GetUniquenessValue"); + + if (unlikely(pValue == nullptr)) + return DDERR_INVALIDPARAMS; + + *pValue = 0; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::ChangeUniquenessValue() { + Logger::debug(">>> DDraw7Surface::ChangeUniquenessValue"); + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::SetPriority(DWORD prio) { + Logger::debug(">>> DDraw7Surface::SetPriority"); + + RefreshD3D9Device(); + if (unlikely(!IsInitialized())) { + Logger::debug("DDraw7Surface::SetPriority: Not yet initialized"); + return m_proxy->SetPriority(prio); + } + + if (unlikely(!m_commonSurf->IsManaged())) + return DDERR_INVALIDOBJECT; + + HRESULT hr = m_proxy->SetPriority(prio); + if (unlikely(FAILED(hr))) + return hr; + + m_d3d9->SetPriority(prio); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetPriority(LPDWORD prio) { + Logger::debug(">>> DDraw7Surface::GetPriority"); + + RefreshD3D9Device(); + if (unlikely(!IsInitialized())) { + Logger::debug("DDraw7Surface::GetPriority: Not yet initialized"); + return m_proxy->GetPriority(prio); + } + + if (unlikely(prio == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonSurf->IsManaged())) + return DDERR_INVALIDOBJECT; + + *prio = m_d3d9->GetPriority(); + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::SetLOD(DWORD lod) { + Logger::debug("<<< DDraw7Surface::SetLOD: Proxy"); + + RefreshD3D9Device(); + if (unlikely(!IsInitialized())) { + Logger::debug("DDraw7Surface::SetLOD: Not yet initialized"); + return m_proxy->SetLOD(lod); + } + + if (unlikely(!m_commonSurf->IsManaged())) + return DDERR_INVALIDOBJECT; + + HRESULT hr = m_proxy->SetLOD(lod); + if (unlikely(FAILED(hr))) + return hr; + + if (m_texture9 != nullptr) { + m_texture9->SetLOD(lod); + } else if (m_cubeMap9 != nullptr) { + m_cubeMap9->SetLOD(lod); + } else { + Logger::warn("DDraw7Surface::SetLOD: Failed to set D3D9 LOD"); + return DDERR_INVALIDOBJECT; + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDraw7Surface::GetLOD(LPDWORD lod) { + Logger::debug(">>> DDraw7Surface::GetLOD"); + + RefreshD3D9Device(); + if (unlikely(!IsInitialized())) { + Logger::debug("DDraw7Surface::GetLOD: Not yet initialized"); + return m_proxy->GetLOD(lod); + } + + if (unlikely(lod == nullptr)) + return DDERR_INVALIDPARAMS; + + if (unlikely(!m_commonSurf->IsManaged())) + return DDERR_INVALIDOBJECT; + + if (likely(m_texture9 != nullptr)) { + *lod = m_texture9->GetLOD(); + } else if (m_cubeMap9 != nullptr) { + *lod = m_cubeMap9->GetLOD(); + } else { + Logger::warn("DDraw7Surface::GetLOD: Failed to get D3D9 LOD"); + return DDERR_INVALIDOBJECT; + } + + return DD_OK; + } + + HRESULT DDraw7Surface::InitializeD3D9RenderTarget() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (unlikely(!IsInitialized())) + hr = InitializeD3D9(true); + + return hr; + } + + HRESULT DDraw7Surface::InitializeD3D9DepthStencil() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (unlikely(!IsInitialized())) + hr = InitializeD3D9(false); + + return hr; + } + + HRESULT DDraw7Surface::InitializeOrUploadD3D9() { + HRESULT hr = DD_OK; + + RefreshD3D9Device(); + + if (likely(IsInitialized())) { + hr = UploadSurfaceData(); + } else { + hr = InitializeD3D9(false); + } + + return hr; + } + + inline void DDraw7Surface::InitializeAndAttachCubeFace( + IDirectDrawSurface7* surf, + d3d9::IDirect3DCubeTexture9* cubeTex9, + d3d9::D3DCUBEMAP_FACES face) { + Com face7; + if (likely(!m_commonIntf->IsWrappedSurface(surf))) { + Com wrappedFace = surf; + try { + face7 = new DDraw7Surface(nullptr, std::move(wrappedFace), m_parent, this, false); + } catch (const DxvkError& e) { + Logger::err("InitializeAndAttachCubeFace: Failed to create wrapped cube face surface"); + Logger::err(e.message()); + } + } else { + face7 = static_cast(surf); + } + + if (likely(face7 != nullptr)) { + Com face9; + cubeTex9->GetCubeMapSurface(face, 0, &face9); + face7->SetD3D9(std::move(face9)); + m_attachedSurfaces.emplace(std::piecewise_construct, + std::forward_as_tuple(face7->GetProxied()), + std::forward_as_tuple(face7.ref())); + } + } + + inline HRESULT DDraw7Surface::InitializeD3D9(const bool initRT) { + if (unlikely(m_d3d9Device == nullptr)) { + Logger::debug("DDraw7Surface::InitializeD3D9: Null device, can't initialize right now"); + return DD_OK; + } + + Logger::debug(str::format("DDraw7Surface::InitializeD3D9: Initializing nr. [[7-", m_surfCount, "]]")); + + const DDSURFACEDESC2* desc2 = m_commonSurf->GetDesc2(); + const d3d9::D3DFORMAT format = m_commonSurf->GetD3D9Format(); + + if (unlikely(desc2->dwHeight == 0 || desc2->dwWidth == 0)) { + Logger::err("DDraw7Surface::InitializeD3D9: Surface has 0 height or width"); + return DDERR_GENERIC; + } + + if (unlikely(format == d3d9::D3DFMT_UNKNOWN)) { + Logger::err("DDraw7Surface::InitializeD3D9: Surface has an unknown format"); + return DDERR_GENERIC; + } + + // Don't initialize P8 textures/surfaces since we don't support them. + // Some applications do require them to be created by ddraw, otherwise + // they will simply fail to start, so just ignore them for now. + if (unlikely(format == d3d9::D3DFMT_P8)) { + static bool s_formatP8ErrorShown; + + if (!std::exchange(s_formatP8ErrorShown, true)) + Logger::warn("DDraw7Surface::InitializeD3D9: Unsupported format D3DFMT_P8"); + + return DD_OK; + + // Similarly, D3DFMT_R3G3B2 isn't supported by D3D9 dxvk, however some + // applications require it to be supported by ddraw, even if they do not + // use it. Simply ignore any D3DFMT_R3G3B2 textures/surfaces for now. + } else if (unlikely(format == d3d9::D3DFMT_R3G3B2)) { + static bool s_formatR3G3B2ErrorShown; + + if (!std::exchange(s_formatR3G3B2ErrorShown, true)) + Logger::warn("DDraw7Surface::InitializeD3D9: Unsupported format D3DFMT_R3G3B2"); + + return DD_OK; + } + + // We need to count the number of actual mips on initialization by going through + // the mip chain, since the dwMipMapCount number may or may not be accurate. I am + // guessing it was intended more as a hint, not neceesarily a set number. + if (m_commonSurf->IsTextureOrCubeMap()) { + IDirectDrawSurface7* mipMap = m_proxy.ptr(); + DDSURFACEDESC2 mipDesc2; + uint16_t mipCount = 1; + + while (mipMap != nullptr) { + IDirectDrawSurface7* parentSurface = mipMap; + mipMap = nullptr; + parentSurface->EnumAttachedSurfaces(&mipMap, ListMipChainSurfaces7Callback); + if (mipMap != nullptr) { + mipCount++; + + mipDesc2 = { }; + mipDesc2.dwSize = sizeof(DDSURFACEDESC2); + mipMap->GetSurfaceDesc(&mipDesc2); + // Ignore multiple 1x1 mips, which apparently can get generated if the + // application gets the dwMipMapCount wrong vs surface dimensions. + if (unlikely(mipDesc2.dwWidth == 1 && mipDesc2.dwHeight == 1)) + break; + } + } + + // Do not worry about maximum supported mip map levels validation, + // because D3D9 will handle this for us and cap them appropriately + if (mipCount > 1) { + Logger::debug(str::format("DDraw7Surface::InitializeD3D9: Found ", mipCount, " mip levels")); + + if (unlikely(mipCount != desc2->dwMipMapCount)) + Logger::debug(str::format("DDraw7Surface::InitializeD3D9: Mismatch with declared ", desc2->dwMipMapCount, " mip levels")); + } + + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) { + Logger::debug("DDraw7Surface::InitializeD3D9: Using auto mip map generation"); + mipCount = 0; + } + + m_commonSurf->SetMipCount(mipCount); + } + + d3d9::D3DPOOL pool = d3d9::D3DPOOL_DEFAULT; + DWORD usage = 0; + + // General surface/texture pool placement + if (desc2->ddsCaps.dwCaps & DDSCAPS_LOCALVIDMEM) + pool = d3d9::D3DPOOL_DEFAULT; + // There's no explicit non-local video memory placement + // per se, but D3DPOOL_MANAGED is close enough + else if ((desc2->ddsCaps.dwCaps & DDSCAPS_NONLOCALVIDMEM) || (desc2->ddsCaps.dwCaps2 & DDSCAPS2_TEXTUREMANAGE)) + pool = d3d9::D3DPOOL_MANAGED; + else if (desc2->ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY) + // We can't know beforehand if a texture is or isn't going to be + // used in SetTexture() calls, and textures placed in D3DPOOL_SYSTEMMEM + // will not work in that context in dxvk, so revert to D3DPOOL_MANAGED. + pool = m_commonSurf->IsTextureOrCubeMap() ? d3d9::D3DPOOL_MANAGED : d3d9::D3DPOOL_SYSTEMMEM; + + // Place all possible render targets in DEFAULT + // + // Note: This is somewhat problematic for textures and cube maps + // which will have D3DUSAGE_RENDERTARGET, but also need to have + // D3DUSAGE_DYNAMIC for locking/uploads to work. The flag combination + // isn't supported in D3D9, but we have a D3D7 exception in place. + // + if (m_commonSurf->IsRenderTarget() || initRT) { + Logger::debug("DDraw7Surface::InitializeD3D9: Usage: D3DUSAGE_RENDERTARGET"); + pool = d3d9::D3DPOOL_DEFAULT; + usage |= D3DUSAGE_RENDERTARGET; + } + // All depth stencils will be created in DEFAULT + if (m_commonSurf->IsDepthStencil()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Usage: D3DUSAGE_DEPTHSTENCIL"); + pool = d3d9::D3DPOOL_DEFAULT; + usage |= D3DUSAGE_DEPTHSTENCIL; + } + + // General usage flags + if (m_commonSurf->IsTextureOrCubeMap()) { + if (pool == d3d9::D3DPOOL_DEFAULT) { + Logger::debug("DDraw7Surface::InitializeD3D9: Usage: D3DUSAGE_DYNAMIC"); + usage |= D3DUSAGE_DYNAMIC; + } + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) { + Logger::debug("DDraw7Surface::InitializeD3D9: Usage: D3DUSAGE_AUTOGENMIPMAP"); + usage |= D3DUSAGE_AUTOGENMIPMAP; + } + } + + const char* poolPlacement = pool == d3d9::D3DPOOL_DEFAULT ? "D3DPOOL_DEFAULT" : + pool == d3d9::D3DPOOL_SYSTEMMEM ? "D3DPOOL_SYSTEMMEM" : "D3DPOOL_MANAGED"; + + Logger::debug(str::format("DDraw7Surface::InitializeD3D9: Placing in: ", poolPlacement)); + + // Use the MSAA type that was determined to be supported during device creation + const d3d9::D3DMULTISAMPLE_TYPE multiSampleType = m_commonIntf->GetMultiSampleType(); + const uint32_t index = m_commonSurf->GetBackBufferIndex(); + + Com surf; + + HRESULT hr = DDERR_GENERIC; + + // Front Buffer + if (m_commonSurf->IsFrontBuffer()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing front buffer..."); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to retrieve front buffer"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Back Buffer + } else if (m_commonSurf->IsBackBufferOrFlippable()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing back buffer..."); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to retrieve back buffer"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Cube maps + } else if (m_commonSurf->IsCubeMap()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing cube map..."); + + Com cubetex; + + hr = m_d3d9Device->CreateCubeTexture( + desc2->dwWidth, m_commonSurf->GetMipCount(), usage, + format, pool, &cubetex, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to create cube map"); + m_cubeMap9 = nullptr; + return hr; + } + + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) + cubetex->SetAutoGenFilterType(d3d9::D3DTEXF_ANISOTROPIC); + + // Always attach the positive X face to this surface + cubetex->GetCubeMapSurface(d3d9::D3DCUBEMAP_FACE_POSITIVE_X, 0, &surf); + m_d3d9 = (std::move(surf)); + + Logger::debug("DDraw7Surface::InitializeD3D9: Retrieving cube map faces"); + + CubeMapAttachedSurfaces cubeMapAttachedSurfaces; + m_proxy->EnumAttachedSurfaces(&cubeMapAttachedSurfaces, EnumAndAttachCubeMapFacesCallback); + + // The positive X surfaces is this surface, so nothing should be retrieved + if (unlikely(cubeMapAttachedSurfaces.positiveX != nullptr)) + Logger::warn("DDraw7Surface::InitializeD3D9: Non-null positive X cube map face"); + + m_cubeMapSurfaces[0] = m_proxy.ptr(); + + // We can't know in advance which faces have been generated, + // so check them one by one, initialize and bind as needed + if (cubeMapAttachedSurfaces.negativeX != nullptr) { + Logger::debug("DDraw7Surface::InitializeD3D9: Detected negative X cube map face"); + m_cubeMapSurfaces[1] = cubeMapAttachedSurfaces.negativeX; + InitializeAndAttachCubeFace(cubeMapAttachedSurfaces.negativeX, cubetex.ptr(), + d3d9::D3DCUBEMAP_FACE_NEGATIVE_X); + } + if (cubeMapAttachedSurfaces.positiveY != nullptr) { + Logger::debug("DDraw7Surface::InitializeD3D9: Detected positive Y cube map face"); + m_cubeMapSurfaces[2] = cubeMapAttachedSurfaces.positiveY; + InitializeAndAttachCubeFace(cubeMapAttachedSurfaces.positiveY, cubetex.ptr(), + d3d9::D3DCUBEMAP_FACE_POSITIVE_Y); + } + if (cubeMapAttachedSurfaces.negativeY != nullptr) { + Logger::debug("DDraw7Surface::InitializeD3D9: Detected negative Y cube map face"); + m_cubeMapSurfaces[3] = cubeMapAttachedSurfaces.negativeY; + InitializeAndAttachCubeFace(cubeMapAttachedSurfaces.negativeY, cubetex.ptr(), + d3d9::D3DCUBEMAP_FACE_NEGATIVE_Y); + } + if (cubeMapAttachedSurfaces.positiveZ != nullptr) { + Logger::debug("DDraw7Surface::InitializeD3D9: Detected positive Z cube map face"); + m_cubeMapSurfaces[4] = cubeMapAttachedSurfaces.positiveZ; + InitializeAndAttachCubeFace(cubeMapAttachedSurfaces.positiveZ, cubetex.ptr(), + d3d9::D3DCUBEMAP_FACE_POSITIVE_Z); + } + if (cubeMapAttachedSurfaces.negativeZ != nullptr) { + Logger::debug("DDraw7Surface::InitializeD3D9: Detected negative Z cube map face"); + m_cubeMapSurfaces[5] = cubeMapAttachedSurfaces.negativeZ; + InitializeAndAttachCubeFace(cubeMapAttachedSurfaces.negativeZ, cubetex.ptr(), + d3d9::D3DCUBEMAP_FACE_NEGATIVE_Z); + } + + Logger::debug("DDraw7Surface::InitializeD3D9: Created cube map"); + m_cubeMap9 = std::move(cubetex); + + // Textures + } else if (m_commonSurf->IsTexture()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing texture..."); + + Com tex; + + hr = m_d3d9Device->CreateTexture( + desc2->dwWidth, desc2->dwHeight, m_commonSurf->GetMipCount(), usage, + format, pool, &tex, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to create texture"); + m_texture9 = nullptr; + return hr; + } + + if (unlikely(m_commonIntf->GetOptions()->autoGenMipMaps)) + tex->SetAutoGenFilterType(d3d9::D3DTEXF_ANISOTROPIC); + + // Attach level 0 to this surface + tex->GetSurfaceLevel(0, &surf); + m_d3d9 = (std::move(surf)); + + Logger::debug("DDraw7Surface::InitializeD3D9: Created texture"); + m_texture9 = std::move(tex); + + // Depth Stencil + } else if (m_commonSurf->IsDepthStencil()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing depth stencil..."); + + hr = m_d3d9Device->CreateDepthStencilSurface( + desc2->dwWidth, desc2->dwHeight, format, + multiSampleType, 0, FALSE, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to create DS"); + m_d3d9 = nullptr; + return hr; + } + + Logger::debug("DDraw7Surface::InitializeD3D9: Created depth stencil surface"); + + m_d3d9 = std::move(surf); + + // Offscreen Plain Surfaces + } else if (m_commonSurf->IsOffScreenPlainSurface()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing offscreen plain surface..."); + + // Sometimes we get passed offscreen plain surfaces which should be tied to the back buffer, + // either as existing RTs or during SetRenderTarget() calls, which are tracked with initRT + if (unlikely(m_commonIntf->IsCurrentRenderTarget(this) || initRT)) { + Logger::debug("DDraw7Surface::InitializeD3D9: Offscreen plain surface is the RT"); + + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to retrieve offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + } else { + hr = m_d3d9Device->CreateOffscreenPlainSurface( + desc2->dwWidth, desc2->dwHeight, format, + pool, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to create offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + } + + m_d3d9 = std::move(surf); + + // Overlays (haven't seen any actual use of overlays in the wild) + } else if (m_commonSurf->IsOverlay()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing overlay..."); + + // Always link overlays to the back buffer + m_d3d9Device->GetBackBuffer(0, index, d3d9::D3DBACKBUFFER_TYPE_MONO, &surf); + + if (unlikely(surf == nullptr)) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to retrieve overlay surface"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // Generic render target + } else if (m_commonSurf->IsRenderTarget()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing render target..."); + + // Must be lockable for blitting to work. Note that + // D3D9 does not allow the creation of lockable RTs when + // using MSAA, but we have a D3D7 exception in place. + hr = m_d3d9Device->CreateRenderTarget( + desc2->dwWidth, desc2->dwHeight, format, + multiSampleType, usage, TRUE, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to create render target"); + m_d3d9 = nullptr; + return hr; + } + + m_d3d9 = std::move(surf); + + // We sometimes get generic surfaces, with only dimensions, format and placement info + } else if (!m_commonSurf->IsNotKnown()) { + Logger::debug("DDraw7Surface::InitializeD3D9: Initializing generic surface..."); + + hr = m_d3d9Device->CreateOffscreenPlainSurface( + desc2->dwWidth, desc2->dwHeight, format, + pool, &surf, nullptr); + + if (unlikely(FAILED(hr))) { + Logger::err("DDraw7Surface::InitializeD3D9: Failed to create offscreen plain surface"); + m_d3d9 = nullptr; + return hr; + } + + Logger::debug("DDraw7Surface::InitializeD3D9: Created offscreen plain surface"); + + m_d3d9 = std::move(surf); + } else { + Logger::warn("DDraw7Surface::InitializeD3D9: Skipping initialization of unknown surface"); + } + + // Depth stencils will not need uploads post initialization + if (likely(!m_commonSurf->IsDepthStencil())) + UploadSurfaceData(); + + return DD_OK; + } + + inline HRESULT DDraw7Surface::UploadSurfaceData() { + Logger::debug(str::format("DDraw7Surface::UploadSurfaceData: Uploading nr. [[7-", m_surfCount, "]]")); + + if (unlikely(m_commonIntf->HasDrawn() && m_commonSurf->IsGuardableSurface())) { + Logger::debug("DDraw7Surface::UploadSurfaceData: Skipping upload"); + return DD_OK; + } + + const d3d9::D3DFORMAT format = m_commonSurf->GetD3D9Format(); + + // Cube maps will also get marked as textures, so need to be handled first + if (unlikely(m_commonSurf->IsCubeMap())) { + // In theory we won't know which faces have been generated, + // so check them one by one, and upload as needed + const uint16_t mipCount = m_commonSurf->GetMipCount(); + if (likely(m_cubeMapSurfaces[0] != nullptr)) { + BlitToD3D9CubeMap(m_cubeMap9.ptr(), format, m_cubeMapSurfaces[0], mipCount); + } else { + Logger::debug("DDraw7Surface::UploadSurfaceData: Positive X face is null, skpping"); + } + if (likely(m_cubeMapSurfaces[1] != nullptr)) { + BlitToD3D9CubeMap(m_cubeMap9.ptr(), format, m_cubeMapSurfaces[1], mipCount); + } else { + Logger::debug("DDraw7Surface::UploadSurfaceData: Negative X face is null, skpping"); + } + if (likely(m_cubeMapSurfaces[2] != nullptr)) { + BlitToD3D9CubeMap(m_cubeMap9.ptr(), format, m_cubeMapSurfaces[2], mipCount); + } else { + Logger::debug("DDraw7Surface::UploadSurfaceData: Positive Y face is null, skpping"); + } + if (likely(m_cubeMapSurfaces[3] != nullptr)) { + BlitToD3D9CubeMap(m_cubeMap9.ptr(), format, m_cubeMapSurfaces[3], mipCount); + } else { + Logger::debug("DDraw7Surface::UploadSurfaceData: Negative Y face is null, skpping"); + } + if (likely(m_cubeMapSurfaces[4] != nullptr)) { + BlitToD3D9CubeMap(m_cubeMap9.ptr(), format, m_cubeMapSurfaces[4], mipCount); + } else { + Logger::debug("DDraw7Surface::UploadSurfaceData: Positive Z face is null, skpping"); + } + if (likely(m_cubeMapSurfaces[5] != nullptr)) { + BlitToD3D9CubeMap(m_cubeMap9.ptr(), format, m_cubeMapSurfaces[5], mipCount); + } else { + Logger::debug("DDraw7Surface::UploadSurfaceData: Negative Z face is null, skpping"); + } + // Blit all the mips for textures + } else if (m_commonSurf->IsTexture()) { + BlitToD3D9Texture(m_texture9.ptr(), format, + m_proxy.ptr(), m_commonSurf->GetMipCount()); + // Blit surfaces directly + } else { + if (unlikely(m_commonSurf->IsDepthStencil())) { + if (likely(m_commonIntf->GetOptions()->uploadDepthStencils)) { + Logger::debug("DDraw7Surface::UploadSurfaceData: Uploading depth stencil"); + } else { + Logger::debug("DDraw7Surface::UploadSurfaceData: Skipping upload of depth stencil"); + return DD_OK; + } + } + + BlitToD3D9Surface(m_d3d9.ptr(), format, m_proxy.ptr()); + } + + return DD_OK; + } + +} diff --git a/src/ddraw/ddraw7/ddraw7_surface.h b/src/ddraw/ddraw7/ddraw7_surface.h new file mode 100644 index 00000000000..cf4558b5366 --- /dev/null +++ b/src/ddraw/ddraw7/ddraw7_surface.h @@ -0,0 +1,237 @@ +#pragma once + +#include "../ddraw_include.h" +#include "../ddraw_wrapped_object.h" + +#include "../ddraw_common_interface.h" +#include "../ddraw_common_surface.h" + +#include "ddraw7_interface.h" + +#include +#include + +namespace dxvk { + + /** + * \brief IDirectDrawSurface7 interface implementation + */ + class DDraw7Surface final : public DDrawWrappedObject { + + public: + + DDraw7Surface( + DDrawCommonSurface* commonSurf, + Com&& surfProxy, + DDraw7Interface* pParent, + DDraw7Surface* pParentSurf, + bool isChildObject); + + ~DDraw7Surface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE AddAttachedSurface(LPDIRECTDRAWSURFACE7 lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE AddOverlayDirtyRect(LPRECT lpRect); + + HRESULT STDMETHODCALLTYPE Blt(LPRECT lpDestRect, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx); + + HRESULT STDMETHODCALLTYPE BltBatch(LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE BltFast(DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans); + + HRESULT STDMETHODCALLTYPE DeleteAttachedSurface(DWORD dwFlags, LPDIRECTDRAWSURFACE7 lpDDSAttachedSurface); + + HRESULT STDMETHODCALLTYPE EnumAttachedSurfaces(LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpEnumSurfacesCallback); + + HRESULT STDMETHODCALLTYPE EnumOverlayZOrders(DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpfnCallback); + + HRESULT STDMETHODCALLTYPE Flip(LPDIRECTDRAWSURFACE7 lpDDSurfaceTargetOverride, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetAttachedSurface(LPDDSCAPS2 lpDDSCaps, LPDIRECTDRAWSURFACE7 *lplpDDAttachedSurface); + + HRESULT STDMETHODCALLTYPE GetBltStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDDSCAPS2 lpDDSCaps); + + HRESULT STDMETHODCALLTYPE GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper); + + HRESULT STDMETHODCALLTYPE GetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE GetDC(HDC *lphDC); + + HRESULT STDMETHODCALLTYPE GetFlipStatus(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetOverlayPosition(LPLONG lplX, LPLONG lplY); + + HRESULT STDMETHODCALLTYPE GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette); + + HRESULT STDMETHODCALLTYPE GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat); + + HRESULT STDMETHODCALLTYPE GetSurfaceDesc(LPDDSURFACEDESC2 lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECTDRAW lpDD, LPDDSURFACEDESC2 lpDDSurfaceDesc); + + HRESULT STDMETHODCALLTYPE IsLost(); + + HRESULT STDMETHODCALLTYPE Lock(LPRECT lpDestRect, LPDDSURFACEDESC2 lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent); + + HRESULT STDMETHODCALLTYPE ReleaseDC(HDC hDC); + + HRESULT STDMETHODCALLTYPE Restore(); + + HRESULT STDMETHODCALLTYPE SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper); + + HRESULT STDMETHODCALLTYPE SetColorKey(DWORD dwFlags, LPDDCOLORKEY lpDDColorKey); + + HRESULT STDMETHODCALLTYPE SetOverlayPosition(LONG lX, LONG lY); + + HRESULT STDMETHODCALLTYPE SetPalette(LPDIRECTDRAWPALETTE lpDDPalette); + + HRESULT STDMETHODCALLTYPE Unlock(LPRECT lpSurfaceData); + + HRESULT STDMETHODCALLTYPE UpdateOverlay(LPRECT lpSrcRect, LPDIRECTDRAWSURFACE7 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx); + + HRESULT STDMETHODCALLTYPE UpdateOverlayDisplay(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE UpdateOverlayZOrder(DWORD dwFlags, LPDIRECTDRAWSURFACE7 lpDDSReference); + + HRESULT STDMETHODCALLTYPE GetDDInterface(LPVOID *lplpDD); + + HRESULT STDMETHODCALLTYPE PageLock(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE PageUnlock(DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetSurfaceDesc(LPDDSURFACEDESC2 lpDDSD, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID tag, LPVOID pData, DWORD cbSize, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID tag, LPVOID pBuffer, LPDWORD pcbBufferSize); + + HRESULT STDMETHODCALLTYPE FreePrivateData(REFGUID tag); + + HRESULT STDMETHODCALLTYPE GetUniquenessValue(LPDWORD pValue); + + HRESULT STDMETHODCALLTYPE ChangeUniquenessValue(); + + HRESULT STDMETHODCALLTYPE SetPriority(DWORD prio); + + HRESULT STDMETHODCALLTYPE GetPriority(LPDWORD prio); + + HRESULT STDMETHODCALLTYPE SetLOD(DWORD lod); + + HRESULT STDMETHODCALLTYPE GetLOD(LPDWORD lod); + + DDrawCommonSurface* GetCommonSurface() const { + return m_commonSurf.ptr(); + } + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf; + } + + d3d9::IDirect3DDevice9* GetD3D9Device() const { + return m_d3d9Device; + } + + d3d9::IDirect3DTexture9* GetD3D9Texture() const { + return m_texture9.ptr(); + } + + d3d9::IDirect3DCubeTexture9* GetD3D9CubeTexture() const { + return m_cubeMap9.ptr(); + } + + DDraw7Surface* GetAttachedDepthStencil() { + // Fast path, since in most cases we already store the required surface + if (likely(m_depthStencil.ptr() != nullptr)) + return m_depthStencil.ptr(); + + DDSCAPS2 caps2; + caps2.dwCaps = DDSCAPS_ZBUFFER; + IDirectDrawSurface7* surface = nullptr; + HRESULT hr = GetAttachedSurface(&caps2, &surface); + if (unlikely(FAILED(hr))) + return nullptr; + + m_depthStencil = reinterpret_cast(surface); + + return m_depthStencil.ptr(); + } + + void ClearAttachedDepthStencil() { + m_depthStencil = nullptr; + } + + void SetParentSurface(DDraw7Surface* surface) { + m_parentSurf = surface; + m_commonSurf->SetIsAttached(true); + } + + void ClearParentSurface() { + m_parentSurf = nullptr; + m_commonSurf->SetIsAttached(false); + } + + HRESULT InitializeD3D9RenderTarget(); + + HRESULT InitializeD3D9DepthStencil(); + + HRESULT InitializeOrUploadD3D9(); + + private: + + inline void InitializeAndAttachCubeFace( + IDirectDrawSurface7* surf, + d3d9::IDirect3DCubeTexture9* cubeTex9, + d3d9::D3DCUBEMAP_FACES face); + + inline HRESULT InitializeD3D9(const bool initRT); + + inline HRESULT UploadSurfaceData(); + + inline void RefreshD3D9Device() { + d3d9::IDirect3DDevice9* d3d9Device = m_commonIntf->GetD3D9Device(); + if (unlikely(m_d3d9Device != d3d9Device)) { + // Check if the device has been recreated and reset all D3D9 resources + if (m_d3d9Device != nullptr) { + Logger::debug("DDraw7Surface: Device context has changed, clearing all D3D9 resources"); + m_cubeMap9 = nullptr; + m_texture9 = nullptr; + m_d3d9 = nullptr; + } + m_d3d9Device = d3d9Device; + } + } + + bool m_isChildObject = false; + + static uint32_t s_surfCount; + uint32_t m_surfCount = 0; + + Com m_commonSurf; + DDrawCommonInterface* m_commonIntf = nullptr; + + DDraw7Surface* m_parentSurf = nullptr; + + d3d9::IDirect3DDevice9* m_d3d9Device = nullptr; + + Com m_cubeMap9; + std::array m_cubeMapSurfaces; + + Com m_texture9; + + // Back buffers will have depth stencil surfaces as attachments (in practice + // I have never seen more than one depth stencil being attached at a time) + Com m_depthStencil; + + // These are attached surfaces, which are typically mips or other types of generated + // surfaces, which need to exist for the entire lifecycle of their parent surface. + // They are implemented with linked list, so for example only one mip level + // will be held in a parent texture, and the next mip level will be held in the previous mip. + std::unordered_map> m_attachedSurfaces; + + }; + +} diff --git a/src/ddraw/ddraw_caps.h b/src/ddraw/ddraw_caps.h new file mode 100644 index 00000000000..2606b7c158c --- /dev/null +++ b/src/ddraw/ddraw_caps.h @@ -0,0 +1,37 @@ +#pragma once + +namespace dxvk::ddrawCaps { + + static constexpr uint32_t MaxClipPlanes = 6; + static constexpr uint32_t MaxTextureDimension = 8192; + + static constexpr uint32_t MaxSimultaneousTextures = 8; + static constexpr uint32_t TextureStageCount = MaxSimultaneousTextures; + static constexpr uint32_t MaxTextureBlendStages = MaxSimultaneousTextures; + + // Most early D3D applications can't handle more than a 2 GB address space + static constexpr uint32_t MaxTextureMemory = 2048; // MB + static constexpr uint32_t ReservedTextureMemory = 8; // MB + + static constexpr uint32_t MaxEnabledLights = 8; + + static constexpr uint8_t IndexBufferCount = 7; + // Index buffer sizes of XXS, XS, S, M, L, XL and XXL, corresponding to 0.1 kb, 0.5 kb, 2 kb, 8 kb, 32 kb, 64 kb and 128 kb + static constexpr UINT IndexCount[IndexBufferCount] = {64, 256, 1024, 4096, 16384, 32768, D3DMAXNUMVERTICES}; + + static constexpr uint8_t NumberOfFOURCCCodes = 6; + static constexpr DWORD SupportedFourCCs[] = + { + MAKEFOURCC('D', 'X', 'T', '1'), + MAKEFOURCC('D', 'X', 'T', '2'), + MAKEFOURCC('D', 'X', 'T', '3'), + MAKEFOURCC('D', 'X', 'T', '4'), + MAKEFOURCC('D', 'X', 'T', '5'), + MAKEFOURCC('Y', 'U', 'Y', '2'), + }; + + // ZBIAS can be an integer from 0 to 16 and needs to be remapped to float + static constexpr float ZBIAS_SCALE = -1.0f / ((1u << 16) - 1); // Consider D16 precision + static constexpr float ZBIAS_SCALE_INV = 1 / ZBIAS_SCALE; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_class_factory.cpp b/src/ddraw/ddraw_class_factory.cpp new file mode 100644 index 00000000000..a26cce45820 --- /dev/null +++ b/src/ddraw/ddraw_class_factory.cpp @@ -0,0 +1,15 @@ +#include "ddraw_class_factory.h" + +namespace dxvk { + + DDrawClassFactory::DDrawClassFactory(FunctionType createInstance) + : m_createInstance ( createInstance ) { + } + + HRESULT STDMETHODCALLTYPE DDrawClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) { + Logger::debug(">>> DDrawClassFactory::CreateInstance"); + + return m_createInstance(pUnkOuter, riid, ppvObject); + } + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_class_factory.h b/src/ddraw/ddraw_class_factory.h new file mode 100644 index 00000000000..6c20a879a71 --- /dev/null +++ b/src/ddraw/ddraw_class_factory.h @@ -0,0 +1,44 @@ +#pragma once + +#include "ddraw_include.h" + +namespace dxvk { + + class DDrawClassFactory : public ComObjectClamp { + + using FunctionType = HRESULT(&)(IUnknown *pUnkOuter, REFIID riid, void **ppvObject); + + public: + + DDrawClassFactory(FunctionType createInstance); + + HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject); + + HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock) { + Logger::warn("!!! DDrawClassFactory::LockServer: Stub"); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDrawClassFactory::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (riid == __uuidof(IUnknown) || riid == __uuidof(IClassFactory)) { + *ppvObject = ref(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + private: + + FunctionType m_createInstance; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_clipper.cpp b/src/ddraw/ddraw_clipper.cpp new file mode 100644 index 00000000000..9be3b26739c --- /dev/null +++ b/src/ddraw/ddraw_clipper.cpp @@ -0,0 +1,53 @@ +#include "ddraw_clipper.h" + +namespace dxvk { + + DDrawClipper::DDrawClipper( + Com&& clipperProxy, + IUnknown* pParent) + : DDrawWrappedObject(pParent, std::move(clipperProxy), nullptr) { + Logger::debug("DDrawClipper: Created a new clipper"); + } + + DDrawClipper::~DDrawClipper() { + Logger::debug("DDrawClipper: A clipper bites the dust"); + } + + HRESULT STDMETHODCALLTYPE DDrawClipper::Initialize(LPDIRECTDRAW lpDD, DWORD dwFlags) { + Logger::debug(">>> DDrawClipper::Initialize"); + + if (unlikely(m_isInitialized)) + return DDERR_ALREADYINITIALIZED; + + m_isInitialized = true; + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawClipper::GetClipList(LPRECT lpRect, LPRGNDATA lpClipList, LPDWORD lpdwSize) { + Logger::debug("<<< DDrawClipper::GetClipList: Proxy"); + return m_proxy->GetClipList(lpRect, lpClipList, lpdwSize); + } + + HRESULT STDMETHODCALLTYPE DDrawClipper::IsClipListChanged(BOOL *lpbChanged) { + Logger::debug("<<< DDrawClipper::IsClipListChanged: Proxy"); + return m_proxy->IsClipListChanged(lpbChanged); + } + + HRESULT STDMETHODCALLTYPE DDrawClipper::SetClipList(LPRGNDATA lpClipList, DWORD dwFlags) { + Logger::debug("<<< DDrawClipper::SetClipList: Proxy"); + return m_proxy->SetClipList(lpClipList, dwFlags); + } + + HRESULT STDMETHODCALLTYPE DDrawClipper::SetHWnd(DWORD dwFlags, HWND hWnd) { + Logger::debug("<<< DDrawClipper::SetHWnd: Proxy"); + return m_proxy->SetHWnd(dwFlags, hWnd); + } + + HRESULT STDMETHODCALLTYPE DDrawClipper::GetHWnd(HWND *lphWnd) { + Logger::debug("<<< DDrawClipper::GetHWnd: Proxy"); + return m_proxy->GetHWnd(lphWnd); + } + +} + diff --git a/src/ddraw/ddraw_clipper.h b/src/ddraw/ddraw_clipper.h new file mode 100644 index 00000000000..bdaca83ed0c --- /dev/null +++ b/src/ddraw/ddraw_clipper.h @@ -0,0 +1,36 @@ +#pragma once + +#include "ddraw_include.h" +#include "ddraw_wrapped_object.h" + +namespace dxvk { + + class DDrawClipper final : public DDrawWrappedObject { + + public: + + DDrawClipper( + Com&& clipperProxy, + IUnknown* pParent); + + ~DDrawClipper(); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECTDRAW lpDD, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE GetClipList(LPRECT lpRect, LPRGNDATA lpClipList, LPDWORD lpdwSize); + + HRESULT STDMETHODCALLTYPE IsClipListChanged(BOOL *lpbChanged); + + HRESULT STDMETHODCALLTYPE SetClipList(LPRGNDATA lpClipList, DWORD dwFlags); + + HRESULT STDMETHODCALLTYPE SetHWnd(DWORD dwFlags, HWND hWnd); + + HRESULT STDMETHODCALLTYPE GetHWnd(HWND *lphWnd); + + private: + + bool m_isInitialized = false; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_common_interface.cpp b/src/ddraw/ddraw_common_interface.cpp new file mode 100644 index 00000000000..eef3e2db713 --- /dev/null +++ b/src/ddraw/ddraw_common_interface.cpp @@ -0,0 +1,328 @@ +#include "ddraw_common_interface.h" + +#include "d3d_common_texture.h" + +#include "ddraw/ddraw_surface.h" +#include "ddraw4/ddraw4_surface.h" +#include "ddraw7/ddraw7_surface.h" + +#include "d3d3/d3d3_device.h" +#include "d3d5/d3d5_device.h" +#include "d3d6/d3d6_device.h" +#include "d3d7/d3d7_device.h" + +#include + +namespace dxvk { + + DDrawCommonInterface::DDrawCommonInterface(const D3DOptions& d3dOptions) + : m_d3dOptions ( d3dOptions ) { + } + + DDrawCommonInterface::~DDrawCommonInterface() { + } + + bool DDrawCommonInterface::IsWrappedSurface(IDirectDrawSurface* surface) const { + if (unlikely(surface == nullptr)) + return false; + + auto it = std::find(m_surfaces.begin(), m_surfaces.end(), surface); + if (likely(it != m_surfaces.end())) + return true; + + return false; + } + + void DDrawCommonInterface::AddWrappedSurface(IDirectDrawSurface* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces.begin(), m_surfaces.end(), surface); + if (unlikely(it != m_surfaces.end())) { + Logger::warn("DDrawCommonInterface::AddWrappedSurface: Pre-existing wrapped surface found"); + } else { + m_surfaces.push_back(surface); + } + } + } + + void DDrawCommonInterface::RemoveWrappedSurface(IDirectDrawSurface* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces.begin(), m_surfaces.end(), surface); + if (likely(it != m_surfaces.end())) { + m_surfaces.erase(it); + } else { + Logger::warn("DDrawCommonInterface::RemoveWrappedSurface: Surface not found"); + } + } + } + + bool DDrawCommonInterface::IsWrappedSurface(IDirectDrawSurface2* surface) const { + if (unlikely(surface == nullptr)) + return false; + + auto it = std::find(m_surfaces2.begin(), m_surfaces2.end(), surface); + if (likely(it != m_surfaces2.end())) + return true; + + return false; + } + + void DDrawCommonInterface::AddWrappedSurface(IDirectDrawSurface2* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces2.begin(), m_surfaces2.end(), surface); + if (unlikely(it != m_surfaces2.end())) { + Logger::warn("DDrawCommonInterface::AddWrappedSurface: Pre-existing wrapped surface found"); + } else { + m_surfaces2.push_back(surface); + } + } + } + + void DDrawCommonInterface::RemoveWrappedSurface(IDirectDrawSurface2* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces2.begin(), m_surfaces2.end(), surface); + if (likely(it != m_surfaces2.end())) { + m_surfaces2.erase(it); + } else { + Logger::warn("DDrawCommonInterface::RemoveWrappedSurface: Surface not found"); + } + } + } + + bool DDrawCommonInterface::IsWrappedSurface(IDirectDrawSurface3* surface) const { + if (unlikely(surface == nullptr)) + return false; + + auto it = std::find(m_surfaces3.begin(), m_surfaces3.end(), surface); + if (likely(it != m_surfaces3.end())) + return true; + + return false; + } + + void DDrawCommonInterface::AddWrappedSurface(IDirectDrawSurface3* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces3.begin(), m_surfaces3.end(), surface); + if (unlikely(it != m_surfaces3.end())) { + Logger::warn("DDrawCommonInterface::AddWrappedSurface: Pre-existing wrapped surface found"); + } else { + m_surfaces3.push_back(surface); + } + } + } + + void DDrawCommonInterface::RemoveWrappedSurface(IDirectDrawSurface3* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces3.begin(), m_surfaces3.end(), surface); + if (likely(it != m_surfaces3.end())) { + m_surfaces3.erase(it); + } else { + Logger::warn("DDrawCommonInterface::RemoveWrappedSurface: Surface not found"); + } + } + } + + bool DDrawCommonInterface::IsWrappedSurface(IDirectDrawSurface4* surface) const { + if (unlikely(surface == nullptr)) + return false; + + auto it = std::find(m_surfaces4.begin(), m_surfaces4.end(), surface); + if (likely(it != m_surfaces4.end())) + return true; + + return false; + } + + void DDrawCommonInterface::AddWrappedSurface(IDirectDrawSurface4* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces4.begin(), m_surfaces4.end(), surface); + if (unlikely(it != m_surfaces4.end())) { + Logger::warn("DDrawCommonInterface::AddWrappedSurface: Pre-existing wrapped surface found"); + } else { + m_surfaces4.push_back(surface); + } + } + } + + void DDrawCommonInterface::RemoveWrappedSurface(IDirectDrawSurface4* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces4.begin(), m_surfaces4.end(), surface); + if (likely(it != m_surfaces4.end())) { + m_surfaces4.erase(it); + } else { + Logger::warn("DDrawCommonInterface::RemoveWrappedSurface: Surface not found"); + } + } + } + + bool DDrawCommonInterface::IsWrappedSurface(IDirectDrawSurface7* surface) const { + if (unlikely(surface == nullptr)) + return false; + + auto it = std::find(m_surfaces7.begin(), m_surfaces7.end(), surface); + if (likely(it != m_surfaces7.end())) + return true; + + return false; + } + + void DDrawCommonInterface::AddWrappedSurface(IDirectDrawSurface7* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces7.begin(), m_surfaces7.end(), surface); + if (unlikely(it != m_surfaces7.end())) { + Logger::warn("DDrawCommonInterface::AddWrappedSurface: Pre-existing wrapped surface found"); + } else { + m_surfaces7.push_back(surface); + } + } + } + + void DDrawCommonInterface::RemoveWrappedSurface(IDirectDrawSurface7* surface) { + if (likely(surface != nullptr)) { + auto it = std::find(m_surfaces7.begin(), m_surfaces7.end(), surface); + if (likely(it != m_surfaces7.end())) { + m_surfaces7.erase(it); + } else { + Logger::warn("DDrawCommonInterface::RemoveWrappedSurface: Surface not found"); + } + } + } + + d3d9::IDirect3DDevice9* DDrawCommonInterface::GetD3D9Device() { + if (m_device7 != nullptr) { + return m_device7->GetD3D9(); + } else if (m_device6 != nullptr) { + return m_device6->GetD3D9(); + } else if (m_device5 != nullptr) { + return m_device5->GetD3D9(); + } else if (m_device3 != nullptr) { + return m_device3->GetD3D9(); + } + + return nullptr; + } + + uint32_t DDrawCommonInterface::GetTotalTextureMemory() { + if (m_device7 != nullptr) { + return m_device7->GetTotalTextureMemory(); + } else if (m_device6 != nullptr) { + return m_device6->GetTotalTextureMemory(); + } else if (m_device5 != nullptr) { + return m_device5->GetTotalTextureMemory(); + } else if (m_device3 != nullptr) { + return m_device3->GetTotalTextureMemory(); + } + + return 0; + } + + d3d9::D3DMULTISAMPLE_TYPE DDrawCommonInterface::GetMultiSampleType() { + if (m_device7 != nullptr) { + return m_device7->GetMultiSampleType(); + } else if (m_device6 != nullptr) { + return m_device6->GetMultiSampleType(); + } else if (m_device5 != nullptr) { + return m_device5->GetMultiSampleType(); + } else if (m_device3 != nullptr) { + return m_device3->GetMultiSampleType(); + } + + return d3d9::D3DMULTISAMPLE_NONE; + } + + d3d9::D3DPRESENT_PARAMETERS DDrawCommonInterface::GetPresentParameters() { + if (m_device7 != nullptr) { + return m_device7->GetPresentParameters(); + } else if (m_device6 != nullptr) { + return m_device6->GetPresentParameters(); + } else if (m_device5 != nullptr) { + return m_device5->GetPresentParameters(); + } else if (m_device3 != nullptr) { + return m_device3->GetPresentParameters(); + } + + return d3d9::D3DPRESENT_PARAMETERS(); + } + + HRESULT DDrawCommonInterface::ResetD3D9Swapchain(d3d9::D3DPRESENT_PARAMETERS* params) { + if (m_device7 != nullptr) { + return m_device7->ResetD3D9Swapchain(params); + } else if (m_device6 != nullptr) { + return m_device6->ResetD3D9Swapchain(params); + } + // D3D3/5 has no way of disabling/re-enabling VSync + + return DDERR_GENERIC; + } + + bool DDrawCommonInterface::IsCurrentRenderTarget(DDrawSurface* surface) const { + return m_device5 != nullptr ? m_device5->GetRenderTarget() == surface : + m_device3 != nullptr ? m_device3->GetRenderTarget() == surface : false; + } + + bool DDrawCommonInterface::IsCurrentRenderTarget(DDraw4Surface* surface) const { + return m_device6 != nullptr ? m_device6->GetRenderTarget() == surface : false; + } + + bool DDrawCommonInterface::IsCurrentRenderTarget(DDraw7Surface* surface) const { + return m_device7 != nullptr ? m_device7->GetRenderTarget() == surface : false; + } + + bool DDrawCommonInterface::IsCurrentD3D9RenderTarget(d3d9::IDirect3DSurface9* surface) const { + if (unlikely(surface == nullptr)) + return false; + + if (m_device7 != nullptr) { + return surface == m_device7->GetRenderTarget()->GetD3D9(); + } else if (m_device6 != nullptr) { + return surface == m_device6->GetRenderTarget()->GetD3D9(); + } else if (m_device5 != nullptr) { + return surface == m_device5->GetRenderTarget()->GetD3D9(); + } else if (m_device3 != nullptr) { + return surface == m_device3->GetRenderTarget()->GetD3D9(); + } + + return false; + } + + bool DDrawCommonInterface::IsCurrentDepthStencil(DDrawSurface* surface) const { + return m_device5 != nullptr ? m_device5->GetDepthStencil() == surface : + m_device3 != nullptr ? m_device3->GetDepthStencil() == surface : false; + } + + bool DDrawCommonInterface::IsCurrentDepthStencil(DDraw4Surface* surface) const { + return m_device6 != nullptr ? m_device6->GetDepthStencil() == surface : false; + } + + bool DDrawCommonInterface::IsCurrentDepthStencil(DDraw7Surface* surface) const { + return m_device7 != nullptr ? m_device7->GetDepthStencil() == surface : false; + } + + bool DDrawCommonInterface::IsCurrentD3D9DepthStencil(d3d9::IDirect3DSurface9* surface) const { + if (unlikely(surface == nullptr)) + return false; + + if (m_device7 != nullptr) { + return surface == m_device7->GetDepthStencil()->GetD3D9(); + } else if (m_device6 != nullptr) { + return surface == m_device6->GetDepthStencil()->GetD3D9(); + } else if (m_device5 != nullptr) { + return surface == m_device5->GetDepthStencil()->GetD3D9(); + } else if (m_device3 != nullptr) { + return surface == m_device3->GetDepthStencil()->GetD3D9(); + } + + return false; + } + + DDrawSurface* DDrawCommonInterface::GetSurfaceFromTextureHandle(D3DTEXTUREHANDLE handle) const { + auto texturesIter = m_textures.find(handle); + + if (unlikely(texturesIter == m_textures.end())) { + Logger::warn(str::format("DDrawCommonInterface::GetSurfaceFromTextureHandle: Invalid handle: ", handle)); + return nullptr; + } + + return texturesIter->second->GetDDSurface(); + } + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_common_interface.h b/src/ddraw/ddraw_common_interface.h new file mode 100644 index 00000000000..1d178c8953f --- /dev/null +++ b/src/ddraw/ddraw_common_interface.h @@ -0,0 +1,337 @@ +#pragma once + +#include "ddraw_include.h" +#include "ddraw_options.h" + +#include +#include + +namespace dxvk { + + class D3DCommonTexture; + + class DDrawCommonSurface; + + class D3D3Interface; + + class DDraw7Interface; + class DDraw4Interface; + class DDraw2Interface; + class DDrawInterface; + + class DDrawSurface; + class DDraw4Surface; + class DDraw7Surface; + + class D3D7Device; + class D3D6Device; + class D3D5Device; + class D3D3Device; + + class DDrawCommonInterface : public ComObjectClamp { + + public: + + DDrawCommonInterface(const D3DOptions& options); + + ~DDrawCommonInterface(); + + bool IsWrappedSurface(IDirectDrawSurface* surface) const; + + void AddWrappedSurface(IDirectDrawSurface* surface); + + void RemoveWrappedSurface(IDirectDrawSurface* surface); + + bool IsWrappedSurface(IDirectDrawSurface2* surface) const; + + void AddWrappedSurface(IDirectDrawSurface2* surface); + + void RemoveWrappedSurface(IDirectDrawSurface2* surface); + + bool IsWrappedSurface(IDirectDrawSurface3* surface) const; + + void AddWrappedSurface(IDirectDrawSurface3* surface); + + void RemoveWrappedSurface(IDirectDrawSurface3* surface); + + bool IsWrappedSurface(IDirectDrawSurface4* surface) const; + + void AddWrappedSurface(IDirectDrawSurface4* surface); + + void RemoveWrappedSurface(IDirectDrawSurface4* surface); + + bool IsWrappedSurface(IDirectDrawSurface7* surface) const; + + void AddWrappedSurface(IDirectDrawSurface7* surface); + + void RemoveWrappedSurface(IDirectDrawSurface7* surface); + + d3d9::IDirect3DDevice9* GetD3D9Device(); + + uint32_t GetTotalTextureMemory(); + + d3d9::D3DMULTISAMPLE_TYPE GetMultiSampleType(); + + d3d9::D3DPRESENT_PARAMETERS GetPresentParameters(); + + HRESULT ResetD3D9Swapchain(d3d9::D3DPRESENT_PARAMETERS* params); + + bool IsCurrentRenderTarget(DDrawSurface* surface) const; + + bool IsCurrentRenderTarget(DDraw4Surface* surface) const; + + bool IsCurrentRenderTarget(DDraw7Surface* surface) const; + + bool IsCurrentD3D9RenderTarget(d3d9::IDirect3DSurface9* surface) const; + + bool IsCurrentDepthStencil(DDrawSurface* surface) const; + + bool IsCurrentDepthStencil(DDraw4Surface* surface) const; + + bool IsCurrentDepthStencil(DDraw7Surface* surface) const; + + bool IsCurrentD3D9DepthStencil(d3d9::IDirect3DSurface9* surface) const; + + DDrawSurface* GetSurfaceFromTextureHandle(D3DTEXTUREHANDLE handle) const; + + void SetFlipRTSurfaceAndFlags(IUnknown* surf, DWORD flags) { + m_flipRTSurf = surf; + m_flipRTFlags = flags; + } + + IUnknown* GetFlipRTSurface() const { + return m_flipRTSurf; + } + + DWORD GetFlipRTFlags() const { + return m_flipRTFlags; + } + + bool HasDrawn() const { + // Returning true here means we skip all proxied back buffer blits, + // whereas returning false means we allow all proxied back buffer blits + return m_d3dOptions.backBufferGuard == D3DBackBufferGuard::Strict ? true : + m_d3dOptions.backBufferGuard == D3DBackBufferGuard::Disabled ? false : m_hasDrawn; + } + + void MarkAsInitialized() { + m_isInitialized = true; + } + + bool IsInitialized() const { + return m_isInitialized; + } + + void UpdateDrawTracking() { + if (unlikely(!m_hasDrawn)) + m_hasDrawn = true; + } + + void ResetDrawTracking() { + if (likely(m_d3dOptions.backBufferGuard != D3DBackBufferGuard::Strict)) + m_hasDrawn = false; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { + *ppvObject = this; + return S_OK; + } + + void SetAdapterIdentifier(const d3d9::D3DADAPTER_IDENTIFIER9& adapterIdentifier9) { + m_adapterIdentifier9 = adapterIdentifier9; + } + + const d3d9::D3DADAPTER_IDENTIFIER9* GetAdapterIdentifier() const { + return &m_adapterIdentifier9; + } + + const D3DOptions* GetOptions() const { + return &m_d3dOptions; + } + + void SetWaitForVBlank(bool waitForVBlank) { + m_waitForVBlank = waitForVBlank; + } + + bool GetWaitForVBlank() const { + return m_waitForVBlank; + } + + void SetPrimarySurface(DDrawCommonSurface* ps) { + m_ps = ps; + } + + DDrawCommonSurface* GetPrimarySurface() { + return m_ps; + } + + void SetCooperativeLevel(HWND hWnd, DWORD dwFlags) { + m_hWnd = hWnd; + m_cooperativeLevel = dwFlags; + } + + DWORD GetCooperativeLevel() const { + return m_cooperativeLevel; + } + + bool IsCooperativeLevelSet() const { + return (m_cooperativeLevel & DDSCL_NORMAL) || + (m_cooperativeLevel & DDSCL_EXCLUSIVE); + } + + HWND GetHWND() const { + return m_hWnd; + } + + DDrawModeSize* GetModeSize() { + return &m_modeSize; + } + + void SetD3D3Interface(D3D3Interface* d3d3Intf) { + m_d3d3Intf = d3d3Intf; + } + + D3D3Interface* GetD3D3Interface() const { + return m_d3d3Intf; + } + + void SetDD7Interface(DDraw7Interface* intf7) { + m_intf7 = intf7; + } + + DDraw7Interface* GetDD7Interface() const { + return m_intf7; + } + + void SetDD4Interface(DDraw4Interface* intf4) { + m_intf4 = intf4; + } + + DDraw4Interface* GetDD4Interface() const { + return m_intf4; + } + + void SetDD2Interface(DDraw2Interface* intf2) { + m_intf2 = intf2; + } + + DDraw2Interface* GetDD2Interface() const { + return m_intf2; + } + + void SetDDInterface(DDrawInterface* intf) { + m_intf = intf; + } + + DDrawInterface* GetDDInterface() const { + return m_intf; + } + + void SetOrigin(IUnknown* origin) { + m_origin = origin; + } + + IUnknown* GetOrigin() const { + return m_origin; + } + + void SetD3D7Device(D3D7Device* device7) { + m_device7 = device7; + } + + D3D7Device* GetD3D7Device() const { + return m_device7; + } + + void SetD3D6Device(D3D6Device* device6) { + m_device6 = device6; + } + + D3D6Device* GetD3D6Device() const { + return m_device6; + } + + void SetD3D5Device(D3D5Device* device5) { + m_device5 = device5; + } + + D3D5Device* GetD3D5Device() const { + return m_device5; + } + + void SetD3D3Device(D3D3Device* device3) { + m_device3 = device3; + } + + D3D3Device* GetD3D3Device() const { + return m_device3; + } + + D3DTEXTUREHANDLE GetNextTextureHandle() { + return ++m_textureHandle; + } + + void EmplaceTexture(D3DCommonTexture* commonTex, D3DTEXTUREHANDLE handle) { + m_textures.emplace(std::piecewise_construct, + std::forward_as_tuple(handle), + std::forward_as_tuple(commonTex)); + } + + void ReleaseTextureHandle(D3DTEXTUREHANDLE handle) { + auto textureIter = m_textures.find(handle); + + if (likely(textureIter != m_textures.end())) + m_textures.erase(textureIter); + } + + private: + + bool m_isInitialized = false; + // Track draw state on the common interface, since the back buffer + // guard should protect against global blits, not device specific ones + bool m_hasDrawn = false; + bool m_waitForVBlank = true; + + DWORD m_cooperativeLevel = 0; + + DDrawCommonSurface* m_ps = nullptr; + HWND m_hWnd = nullptr; + DDrawModeSize m_modeSize = { }; + + IUnknown* m_flipRTSurf = nullptr; + DWORD m_flipRTFlags = 0; + + d3d9::D3DADAPTER_IDENTIFIER9 m_adapterIdentifier9 = { }; + + D3DOptions m_d3dOptions; + + D3D3Interface* m_d3d3Intf = nullptr; + + // Track all possible last used D3D devices + D3D7Device* m_device7 = nullptr; + D3D6Device* m_device6 = nullptr; + D3D5Device* m_device5 = nullptr; + D3D3Device* m_device3 = nullptr; + + // Track all possible instance versions of the same object + DDraw7Interface* m_intf7 = nullptr; + DDraw4Interface* m_intf4 = nullptr; + DDraw2Interface* m_intf2 = nullptr; + DDrawInterface* m_intf = nullptr; + + // Track the origin surface, as in the DDraw surface + // that gets created through a DirectDrawCreate(Ex) call + IUnknown* m_origin = nullptr; + + std::vector m_surfaces7; + std::vector m_surfaces4; + std::vector m_surfaces3; + std::vector m_surfaces2; + std::vector m_surfaces; + + std::atomic m_textureHandle = 0; + std::unordered_map m_textures; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_common_surface.cpp b/src/ddraw/ddraw_common_surface.cpp new file mode 100644 index 00000000000..54da3685576 --- /dev/null +++ b/src/ddraw/ddraw_common_surface.cpp @@ -0,0 +1,61 @@ +#include "ddraw_common_surface.h" + +#include "ddraw7/ddraw7_surface.h" +#include "ddraw4/ddraw4_surface.h" +#include "ddraw2/ddraw3_surface.h" +#include "ddraw2/ddraw2_surface.h" +#include "ddraw/ddraw_surface.h" + +namespace dxvk { + + DDrawCommonSurface::DDrawCommonSurface(DDrawCommonInterface* commonIntf) + : m_commonIntf ( commonIntf ) { + } + + DDrawCommonSurface::~DDrawCommonSurface() { + if (unlikely(IsPrimarySurface() && m_commonIntf->GetPrimarySurface() == this)) + m_commonIntf->SetPrimarySurface(nullptr); + } + + HRESULT DDrawCommonSurface::RefreshSurfaceDescripton() { + HRESULT hr; + + DDSURFACEDESC2 desc2; + desc2.dwSize = sizeof(DDSURFACEDESC2); + + if (m_surf7 != nullptr) { + hr = m_surf7->GetProxied()->GetSurfaceDesc(&desc2); + if (unlikely(FAILED(hr))) + return hr; + m_desc2 = desc2; + } else if (m_surf4 != nullptr) { + hr = m_surf4->GetProxied()->GetSurfaceDesc(&desc2); + if (unlikely(FAILED(hr))) + return hr; + m_desc2 = desc2; + } + + DDSURFACEDESC desc; + desc.dwSize = sizeof(DDSURFACEDESC); + + if (m_surf != nullptr) { + hr = m_surf->GetProxied()->GetSurfaceDesc(&desc); + if (unlikely(FAILED(hr))) + return hr; + m_desc = desc; + } else if (m_surf2 != nullptr) { + hr = m_surf2->GetProxied()->GetSurfaceDesc(&desc); + if (unlikely(FAILED(hr))) + return hr; + m_desc = desc; + } else if (m_surf3 != nullptr) { + hr = m_surf3->GetProxied()->GetSurfaceDesc(&desc); + if (unlikely(FAILED(hr))) + return hr; + m_desc = desc; + } + + return DD_OK; + } + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_common_surface.h b/src/ddraw/ddraw_common_surface.h new file mode 100644 index 00000000000..b25ae1e7ff2 --- /dev/null +++ b/src/ddraw/ddraw_common_surface.h @@ -0,0 +1,403 @@ +#pragma once + +#include "ddraw_include.h" +#include "ddraw_format.h" + +#include "ddraw_common_interface.h" + +#include "ddraw_clipper.h" +#include "ddraw_palette.h" + +namespace dxvk { + + class DDraw7Surface; + class DDraw4Surface; + class DDraw3Surface; + class DDraw2Surface; + class DDrawSurface; + + class DDrawCommonSurface : public ComObjectClamp { + + public: + + DDrawCommonSurface(DDrawCommonInterface* commonIntf); + + ~DDrawCommonSurface(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { + *ppvObject = this; + return S_OK; + } + + HRESULT RefreshSurfaceDescripton(); + + DDrawCommonInterface* GetCommonInterface() const { + return m_commonIntf.ptr(); + } + + bool IsDesc2Set() const { + return m_isDesc2Set; + } + + void SetDesc2(const DDSURFACEDESC2& desc2) { + m_desc2 = desc2; + m_isDesc2Set = true; + m_format9 = ConvertFormat(m_desc2.ddpfPixelFormat); + // determine and cache various frequently used flag combinations + m_isTextureOrCubeMap = IsTexture() || IsCubeMap(); + m_isBackBufferOrFlippable = !IsFrontBuffer() && (IsBackBuffer() || IsFlippableSurface()); + m_isRenderTarget = IsFrontBuffer() || IsBackBuffer() || IsFlippableSurface() || Is3DSurface(); + m_isForwardableSurface = IsFrontBuffer() || IsBackBuffer() || IsFlippableSurface() + || IsDepthStencil() || IsOffScreenPlainSurface(); + m_isGuardableSurface = IsPrimarySurface() || IsFrontBuffer() + || IsBackBuffer() || IsFlippableSurface(); + } + + const DDSURFACEDESC2* GetDesc2() const { + return &m_desc2; + } + + bool IsDescSet() const { + return m_isDescSet; + } + + void SetDesc(const DDSURFACEDESC& desc) { + m_desc = desc; + m_isDescSet = true; + m_format9 = ConvertFormat(m_desc.ddpfPixelFormat); + // determine and cache various frequently used flag combinations + m_isBackBufferOrFlippable = !IsFrontBuffer() && (IsBackBuffer() || IsFlippableSurface()); + m_isRenderTarget = IsFrontBuffer() || IsBackBuffer() || IsFlippableSurface() || Is3DSurface(); + m_isForwardableSurface = IsFrontBuffer() || IsBackBuffer() || IsFlippableSurface() + || IsDepthStencil() || IsOffScreenPlainSurface(); + m_isGuardableSurface = IsPrimarySurface() || IsFrontBuffer() + || IsBackBuffer() || IsFlippableSurface(); + } + + const DDSURFACEDESC* GetDesc() const { + return &m_desc; + } + + bool IsAlphaFormat() const { + return ((m_desc2.dwFlags & DDSD_PIXELFORMAT) && (m_desc2.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS)) + || ((m_desc.dwFlags & DDSD_PIXELFORMAT) && (m_desc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS)); + } + + bool HasValidColorKey() const { + return (m_desc2.dwFlags & DDSD_CKSRCBLT) || (m_desc.dwFlags & DDSD_CKSRCBLT); + } + + const DDCOLORKEY* GetColorKey() const { + return (m_desc2.dwFlags & DDSD_CKSRCBLT) ? &m_desc2.ddckCKSrcBlt : &m_desc.ddckCKSrcBlt; + } + + DDCOLORKEY GetColorKeyNormalized() const { + const DDPIXELFORMAT* pixelFormat = (m_desc2.dwFlags & DDSD_PIXELFORMAT) ? &m_desc2.ddpfPixelFormat : &m_desc.ddpfPixelFormat; + const DDCOLORKEY* colorKey = (m_desc2.dwFlags & DDSD_CKSRCBLT) ? &m_desc2.ddckCKSrcBlt : &m_desc.ddckCKSrcBlt; + + // Empire of the Ants relies on us using the "Low" color space DWORD + return ColorKeyToRGB(pixelFormat, colorKey->dwColorSpaceLowValue); + } + + d3d9::D3DFORMAT GetD3D9Format() const { + return m_format9; + } + + float GetNormalizedFloatDepth(DWORD input) const { + DWORD max = m_format9 != d3d9::D3DFMT_D16 ? std::numeric_limits::max() + : std::numeric_limits::max(); + return static_cast(input) / static_cast(max); + } + + uint16_t GetMipCount() const { + return m_mipCount; + } + + void SetMipCount(uint16_t mipCount) { + m_mipCount = mipCount; + } + + uint32_t GetBackBufferIndex() const { + return m_backBufferIndex; + } + + void IncrementBackBufferIndex(uint32_t index) { + m_backBufferIndex = index + 1; + } + + bool HasDirtyMipMaps() const { + return m_dirtyMipMaps; + } + + void DirtyMipMaps() { + m_dirtyMipMaps = true; + } + + void UnDirtyMipMaps() { + m_dirtyMipMaps = false; + } + + bool IsAttached() const { + return m_isAttached; + } + + void SetIsAttached(bool isAttached) { + m_isAttached = isAttached; + } + + void SetClipper(DDrawClipper* clipper) { + m_clipper = clipper; + } + + DDrawClipper* GetClipper() const { + return m_clipper.ptr(); + } + + void SetPalette(DDrawPalette* palette) { + if (palette == nullptr) + m_palette->SetCommonSurface(nullptr); + + m_palette = palette; + + if (m_palette != nullptr) + m_palette->SetCommonSurface(this); + } + + DDrawPalette* GetPalette() const { + return m_palette.ptr(); + } + + void SetDD7Surface(DDraw7Surface* surf7) { + m_surf7 = surf7; + } + + DDraw7Surface* GetDD7Surface() const { + return m_surf7; + } + + void SetDD4Surface(DDraw4Surface* surf4) { + m_surf4 = surf4; + } + + DDraw4Surface* GetDD4Surface() const { + return m_surf4; + } + + void SetDD3Surface(DDraw3Surface* surf3) { + m_surf3 = surf3; + } + + DDraw3Surface* GetDD3Surface() const { + return m_surf3; + } + + void SetDD2Surface(DDraw2Surface* surf2) { + m_surf2 = surf2; + } + + DDraw2Surface* GetDD2Surface() const { + return m_surf2; + } + + void SetDDSurface(DDrawSurface* surf) { + m_surf = surf; + } + + DDrawSurface* GetDDSurface() const { + return m_surf; + } + + void SetOrigin(IUnknown* origin) { + m_origin = origin; + } + + IUnknown* GetOrigin() const { + return m_origin; + } + + bool IsComplex() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_COMPLEX + || m_desc.ddsCaps.dwCaps & DDSCAPS_COMPLEX; + } + + bool IsPrimarySurface() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE + || m_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE; + } + + bool IsFrontBuffer() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_FRONTBUFFER + || m_desc.ddsCaps.dwCaps & DDSCAPS_FRONTBUFFER; + } + + bool IsBackBuffer() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_BACKBUFFER + || m_desc.ddsCaps.dwCaps & DDSCAPS_BACKBUFFER; + } + + bool IsDepthStencil() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_ZBUFFER + || m_desc.ddsCaps.dwCaps & DDSCAPS_ZBUFFER; + } + + bool IsOffScreenPlainSurface() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_OFFSCREENPLAIN + || m_desc.ddsCaps.dwCaps & DDSCAPS_OFFSCREENPLAIN; + } + + bool IsTexture() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_TEXTURE + || m_desc.ddsCaps.dwCaps & DDSCAPS_TEXTURE; + } + + bool IsOverlay() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_OVERLAY + || m_desc.ddsCaps.dwCaps & DDSCAPS_OVERLAY; + } + + bool Is3DSurface() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_3DDEVICE + || m_desc.ddsCaps.dwCaps & DDSCAPS_3DDEVICE; + } + + bool IsTextureMip() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_MIPMAP + || m_desc2.ddsCaps.dwCaps2 & DDSCAPS2_MIPMAPSUBLEVEL + || m_desc.ddsCaps.dwCaps & DDSCAPS_MIPMAP; + } + + bool IsCubeMap() const { + return m_desc2.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP; + } + + bool IsFlippableSurface() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_FLIP + || m_desc.ddsCaps.dwCaps & DDSCAPS_FLIP; + } + + bool IsNotKnown() const { + return !(m_desc2.dwFlags & DDSD_CAPS) + && !(m_desc.dwFlags & DDSD_CAPS); + } + + bool IsManaged() const { + return m_desc2.ddsCaps.dwCaps2 & DDSCAPS2_TEXTUREMANAGE; + } + + bool IsInSystemMemory() const { + return m_desc2.ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY + || m_desc.ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY; + } + + bool HasColorKey() const { + return (m_desc2.dwFlags & DDSD_CKSRCBLT || + m_desc.dwFlags & DDSD_CKSRCBLT); + } + + bool IsTextureOrCubeMap() const { + return m_isTextureOrCubeMap; + } + + bool IsBackBufferOrFlippable() const { + return m_isBackBufferOrFlippable; + } + + bool IsRenderTarget() const { + return m_isRenderTarget; + } + + bool IsForwardableSurface() const { + return m_isForwardableSurface; + } + + bool IsGuardableSurface() const { + return m_isGuardableSurface; + } + + HRESULT ValidateRTUsage() const { + // Render targets require the DDSCAPS_3DDEVICE flag + if (unlikely(!Is3DSurface())) { + Logger::err("DDrawCommonInterface::ValidateRTUsage: Missing DDSCAPS_3DDEVICE"); + return DDERR_INVALIDCAPS; + } + + // Depth stencil surfaces can't be set as render targets + if (unlikely(IsDepthStencil())) { + Logger::err("DDrawCommonInterface::ValidateRTUsage: Invalid DDSCAPS_ZBUFFER"); + return DDERR_INVALIDCAPS; + } + + // TODO: Render targets must not be created in system memory on HAL/HAL T&L devices + /*if (unlikely(IsInSystemMemory())) { + Logger::err("DDrawCommonInterface::ValidateRTUsage: Invalid DDSCAPS_SYSTEMMEMORY"); + return D3DERR_SURFACENOTINVIDMEM; + }*/ + + return DD_OK; + } + + void ListSurfaceDetails() const { + const char* type = "generic surface"; + + if (IsFrontBuffer()) type = "front buffer"; + else if (IsBackBuffer()) type = "back buffer"; + else if (IsTextureMip()) type = "texture mipmap"; + else if (IsTexture()) type = "texture"; + else if (IsDepthStencil()) type = "depth stencil"; + else if (IsOffScreenPlainSurface()) type = "offscreen plain surface"; + else if (IsOverlay()) type = "overlay"; + else if (Is3DSurface()) type = "render target"; + else if (IsPrimarySurface()) type = "primary surface"; + else if (IsNotKnown()) type = "unknown"; + + Logger::debug(str::format(" Type: ", type)); + Logger::debug(str::format(" Dimensions: ", m_desc.dwWidth, "x", m_desc.dwHeight)); + Logger::debug(str::format(" Format: ", GetD3D9Format())); + Logger::debug(str::format(" IsComplex: ", IsComplex() ? "yes" : "no")); + Logger::debug(str::format(" HasMipMaps: ", m_desc.dwMipMapCount ? "yes" : "no")); + Logger::debug(str::format(" IsAttached: ", IsAttached() ? "yes" : "no")); + if (IsFrontBuffer()) + Logger::debug(str::format(" BackBuffers: ", m_desc.dwBackBufferCount)); + if (HasColorKey()) + Logger::debug(str::format(" ColorKey: ", GetColorKey()->dwColorSpaceLowValue)); + } + + private: + + bool m_dirtyMipMaps = false; + bool m_isAttached = false; + bool m_isDesc2Set = false; + bool m_isDescSet = false; + + bool m_isTextureOrCubeMap = false; + bool m_isBackBufferOrFlippable = false; + bool m_isRenderTarget = false; + bool m_isForwardableSurface = false; + bool m_isGuardableSurface = false; + + uint16_t m_mipCount = 1; + uint32_t m_backBufferIndex = 0; + + DDSURFACEDESC m_desc = { }; + DDSURFACEDESC2 m_desc2 = { }; + d3d9::D3DFORMAT m_format9 = d3d9::D3DFMT_UNKNOWN; + + Com m_clipper; + Com m_palette; + + Com m_commonIntf; + + // Track all possible surface versions of the same object + DDraw7Surface* m_surf7 = nullptr; + DDraw4Surface* m_surf4 = nullptr; + DDraw3Surface* m_surf3 = nullptr; + DDraw2Surface* m_surf2 = nullptr; + DDrawSurface* m_surf = nullptr; + + // Track the origin surface, as in the DDraw surface + // that gets created through a CreateSurface call + IUnknown* m_origin = nullptr; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_format.h b/src/ddraw/ddraw_format.h new file mode 100644 index 00000000000..ad5cb268dd7 --- /dev/null +++ b/src/ddraw/ddraw_format.h @@ -0,0 +1,910 @@ +#pragma once + +#include "ddraw_include.h" + +#include +#include +#include + +namespace dxvk { + + template + static void CopyToStringArray(char (&dst)[N], const char* src) { + str::strlcpy(dst, src, N); + } + + struct CubeMapAttachedSurfaces { + IDirectDrawSurface7* positiveX = nullptr; + IDirectDrawSurface7* negativeX = nullptr; + IDirectDrawSurface7* positiveY = nullptr; + IDirectDrawSurface7* negativeY = nullptr; + IDirectDrawSurface7* positiveZ = nullptr; + IDirectDrawSurface7* negativeZ = nullptr; + }; + + struct AttachedSurface { + IDirectDrawSurface* surface = nullptr; + DDSURFACEDESC desc = { }; + }; + + struct AttachedSurface4 { + IDirectDrawSurface4* surface4 = nullptr; + DDSURFACEDESC2 desc2 = { }; + }; + + struct AttachedSurface7 { + IDirectDrawSurface7* surface7 = nullptr; + DDSURFACEDESC2 desc2 = { }; + }; + + inline bool IsCubeMapFace(DDSURFACEDESC2* desc) { + return desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX + || desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX + || desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY + || desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY + || desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ + || desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ; + } + + inline d3d9::D3DCUBEMAP_FACES GetCubemapFace(DDSURFACEDESC2* desc) { + if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX) return d3d9::D3DCUBEMAP_FACE_POSITIVE_X; + if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX) return d3d9::D3DCUBEMAP_FACE_NEGATIVE_X; + if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY) return d3d9::D3DCUBEMAP_FACE_POSITIVE_Y; + if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY) return d3d9::D3DCUBEMAP_FACE_NEGATIVE_Y; + if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ) return d3d9::D3DCUBEMAP_FACE_POSITIVE_Z; + if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ) return d3d9::D3DCUBEMAP_FACE_NEGATIVE_Z; + return d3d9::D3DCUBEMAP_FACE_POSITIVE_X; + } + + inline d3d9::D3DFORMAT ConvertFormat(DDPIXELFORMAT& fmt) { + if (fmt.dwFlags & DDPF_RGB) { + Logger::debug(str::format("ConvertFormat: fmt.dwRGBBitCount: ", fmt.dwRGBBitCount)); + Logger::debug(str::format("ConvertFormat: fmt.dwRGBAlphaBitMask: ", fmt.dwRGBAlphaBitMask)); + Logger::debug(str::format("ConvertFormat: fmt.dwRBitMask: ", fmt.dwRBitMask)); + Logger::debug(str::format("ConvertFormat: fmt.dwGBitMask: ", fmt.dwGBitMask)); + Logger::debug(str::format("ConvertFormat: fmt.dwBBitMask: ", fmt.dwBBitMask)); + + switch (fmt.dwRGBBitCount) { + case 8: + // R: 1110 0000 + return (fmt.dwFlags & DDPF_PALETTEINDEXED8) ? d3d9::D3DFMT_P8 : d3d9::D3DFMT_R3G3B2; + case 16: { + switch (fmt.dwRBitMask) { + case (0xF << 8): + // A: 1111 0000 0000 0000 + // R: 0000 1111 0000 0000 + return d3d9::D3DFMT_A4R4G4B4; + case (0x1F << 10): + // A: 1000 0000 0000 0000 + // R: 0111 1100 0000 0000 + return fmt.dwRGBAlphaBitMask ? d3d9::D3DFMT_A1R5G5B5 : d3d9::D3DFMT_X1R5G5B5; + case (0x1F << 11): + // R: 1111 1000 0000 0000 + return d3d9::D3DFMT_R5G6B5; + } + Logger::warn("ConvertFormat: Unhandled dwRGBBitCount 16 format"); + return d3d9::D3DFMT_UNKNOWN; + } + // Drakan: Order of the Flame uses a dwRGBBitCount of 24 + // to request for D3DFMT_X8R8G8B8, based on provided bitmasks + case 24: + return d3d9::D3DFMT_X8R8G8B8; + case 32: { + // A: 1111 1111 0000 0000 0000 0000 0000 0000 + // R: 0000 0000 1111 1111 0000 0000 0000 0000 + return fmt.dwRGBAlphaBitMask ? d3d9::D3DFMT_A8R8G8B8 : d3d9::D3DFMT_X8R8G8B8; + } + } + Logger::warn("ConvertFormat: Unhandled dwRGBBitCount format"); + return d3d9::D3DFMT_UNKNOWN; + + // Depth formats will traditionally store stencil info in the MSB, however + // some games will apparently try to use the LSB, so handle both cases + } else if (fmt.dwFlags & DDPF_ZBUFFER) { + Logger::debug(str::format("ConvertFormat: fmt.dwZBufferBitDepth: ", fmt.dwZBufferBitDepth)); + Logger::debug(str::format("ConvertFormat: fmt.dwZBitMask: ", fmt.dwZBitMask)); + Logger::debug(str::format("ConvertFormat: fmt.dwStencilBitMask: ", fmt.dwStencilBitMask)); + + switch (fmt.dwZBufferBitDepth) { + case 16: { + switch (fmt.dwStencilBitMask) { + case 0: + // D: 1111 1111 1111 1111 + // S: 0000 0000 0000 0000 + return d3d9::D3DFMT_D16; + case 0x1: + case (0x1 << 15): + // D: 0111 1111 1111 1111 + // S: 1000 0000 0000 0000 + Logger::warn("ConvertFormat: Unsupported format D3DFMT_D15S1"); + return d3d9::D3DFMT_D16; + } + Logger::warn("ConvertFormat: Unhandled dwStencilBitMask 16 format"); + return d3d9::D3DFMT_UNKNOWN; + } + // We don't support or expose a 24-bit depth stencil per se, + // but some applications request one anyway. Use 32-bit depth. + case 24: + case 32: { + switch (fmt.dwStencilBitMask) { + case 0: { + switch (fmt.dwZBitMask) { + case 0xFFFFFFFF: + // D: 1111 1111 1111 1111 1111 1111 1111 1111 + // S: 0000 0000 0000 0000 0000 0000 0000 0000 + Logger::warn("ConvertFormat: Unsupported format D3DFMT_D32"); + return d3d9::D3DFMT_D24X8; + case 0xFFFFFF: + case (DWORD(0xFFFFFF << 8)): + // D: 0000 0000 1111 1111 1111 1111 1111 1111 + // S: 0000 0000 0000 0000 0000 0000 0000 0000 + return d3d9::D3DFMT_D24X8; + } + Logger::warn("ConvertFormat: Unhandled dwZBitMask 24/32 format"); + return d3d9::D3DFMT_UNKNOWN; + } + case 0xFF: + case (DWORD(0xFF << 24)): + // D: 0000 0000 1111 1111 1111 1111 1111 1111 + // S: 1111 1111 0000 0000 0000 0000 0000 0000 + return d3d9::D3DFMT_D24S8; + case 0xF: + case (DWORD(0xF << 24)): + // D: 0000 0000 1111 1111 1111 1111 1111 1111 + // S: 1111 0000 0000 0000 0000 0000 0000 0000 + Logger::warn("ConvertFormat: Unsupported format D3DFMT_D24X4S4"); + return d3d9::D3DFMT_D24S8; + } + Logger::warn("ConvertFormat: Unhandled dwStencilBitMask 24/32 format"); + return d3d9::D3DFMT_UNKNOWN; + } + } + Logger::warn("ConvertFormat: Unhandled dwZBufferBitDepth format"); + return d3d9::D3DFMT_UNKNOWN; + + } else if (fmt.dwFlags & DDPF_LUMINANCE) { + Logger::debug(str::format("ConvertFormat: fmt.dwLuminanceBitCount: ", fmt.dwLuminanceBitCount)); + Logger::debug(str::format("ConvertFormat: fmt.dwLuminanceAlphaBitMask: ", fmt.dwLuminanceAlphaBitMask)); + Logger::debug(str::format("ConvertFormat: fmt.dwLuminanceBitMask: ", fmt.dwLuminanceBitMask)); + + switch (fmt.dwLuminanceBitCount) { + case 8: { + switch (fmt.dwLuminanceBitMask) { + case (0xF): + // L: 1111 1111 + return d3d9::D3DFMT_L8; + case (0x8): + // A: 1111 0000 + // L: 0000 1111 + return d3d9::D3DFMT_A4L4; + } + Logger::warn("ConvertFormat: Unhandled dwLuminanceBitCount 8 format"); + return d3d9::D3DFMT_UNKNOWN; + } + case 16: + // A: 1111 1111 0000 0000 + // L: 0000 0000 1111 1111 + return d3d9::D3DFMT_A8L8; + } + Logger::warn("ConvertFormat: Unhandled dwLuminanceBitCount format"); + return d3d9::D3DFMT_UNKNOWN; + + } else if (fmt.dwFlags & DDPF_BUMPDUDV) { + Logger::debug(str::format("ConvertFormat: fmt.dwBumpBitCount: ", fmt.dwBumpBitCount)); + Logger::debug(str::format("ConvertFormat: fmt.dwBumpLuminanceBitMask: ", fmt.dwBumpLuminanceBitMask)); + Logger::debug(str::format("ConvertFormat: fmt.dwBumpDvBitMask: ", fmt.dwBumpDvBitMask)); + Logger::debug(str::format("ConvertFormat: fmt.dwBumpDuBitMask: ", fmt.dwBumpDuBitMask)); + + switch (fmt.dwBumpBitCount) { + case 16: { + // L: 1111 1100 0000 0000 + // V: 0000 0011 1110 0000 + // U: 0000 0000 0001 1111 + // + // L: 0000 0000 0000 0000 + // V: 1111 1111 0000 0000 + // U: 0000 0000 1111 1111 + return fmt.dwBumpLuminanceBitMask ? d3d9::D3DFMT_L6V5U5 : d3d9::D3DFMT_V8U8; + } + case 32: { + // A: 0000 0000 0000 0000 0000 0000 0000 0000 + // L: 0000 0000 1111 1111 0000 0000 0000 0000 + // V: 0000 0000 0000 0000 1111 1111 0000 0000 + // U: 0000 0000 0000 0000 0000 0000 1111 1111 + return d3d9::D3DFMT_X8L8V8U8; + } + } + Logger::warn("ConvertFormat: Unhandled dwBumpBitCount format"); + return d3d9::D3DFMT_UNKNOWN; + + } else if (fmt.dwFlags & DDPF_FOURCC) { + switch (fmt.dwFourCC) { + case MAKEFOURCC('D', 'X', 'T', '1'): + return d3d9::D3DFMT_DXT1; + case MAKEFOURCC('D', 'X', 'T', '2'): + return d3d9::D3DFMT_DXT2; + case MAKEFOURCC('D', 'X', 'T', '3'): + return d3d9::D3DFMT_DXT3; + case MAKEFOURCC('D', 'X', 'T', '4'): + return d3d9::D3DFMT_DXT4; + case MAKEFOURCC('D', 'X', 'T', '5'): + return d3d9::D3DFMT_DXT5; + case MAKEFOURCC('Y', 'U', 'Y', '2'): + return d3d9::D3DFMT_YUY2; + } + Logger::warn("ConvertFormat: Unhandled FOURCC payload"); + return d3d9::D3DFMT_UNKNOWN; + } + + Logger::warn("ConvertFormat: Unhandled bit payload"); + return d3d9::D3DFMT_UNKNOWN; + } + + inline DDPIXELFORMAT GetTextureFormat (d3d9::D3DFORMAT format) { + DDPIXELFORMAT tformat = { }; + tformat.dwSize = sizeof(DDPIXELFORMAT); + + switch (format) { + case d3d9::D3DFMT_A8R8G8B8: + tformat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; + tformat.dwRGBBitCount = 32; + tformat.dwRGBAlphaBitMask = 0xff000000; + tformat.dwRBitMask = 0x00ff0000; + tformat.dwGBitMask = 0x0000ff00; + tformat.dwBBitMask = 0x000000ff; + break; + + case d3d9::D3DFMT_X8R8G8B8: + tformat.dwFlags = DDPF_RGB; + tformat.dwRGBBitCount = 32; + tformat.dwRGBAlphaBitMask = 0x00000000; + tformat.dwRBitMask = 0x00ff0000; + tformat.dwGBitMask = 0x0000ff00; + tformat.dwBBitMask = 0x000000ff; + break; + + case d3d9::D3DFMT_R5G6B5: + tformat.dwFlags = DDPF_RGB; + tformat.dwRGBBitCount = 16; + tformat.dwRGBAlphaBitMask = 0x0000; + tformat.dwRBitMask = 0xf800; + tformat.dwGBitMask = 0x07e0; + tformat.dwBBitMask = 0x001f; + break; + + case d3d9::D3DFMT_X1R5G5B5: + tformat.dwFlags = DDPF_RGB; + tformat.dwRGBBitCount = 16; + tformat.dwRGBAlphaBitMask = 0x0000; + tformat.dwRBitMask = 0x7c00; + tformat.dwGBitMask = 0x03e0; + tformat.dwBBitMask = 0x001f; + break; + + case d3d9::D3DFMT_A1R5G5B5: + tformat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; + tformat.dwRGBBitCount = 16; + tformat.dwRGBAlphaBitMask = 0x8000; + tformat.dwRBitMask = 0x7c00; + tformat.dwGBitMask = 0x03e0; + tformat.dwBBitMask = 0x001f; + break; + + case d3d9::D3DFMT_A4R4G4B4: + tformat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; + tformat.dwRGBBitCount = 16; + tformat.dwRGBAlphaBitMask = 0xf000; + tformat.dwRBitMask = 0x0f00; + tformat.dwGBitMask = 0x00f0; + tformat.dwBBitMask = 0x000f; + break; + + case d3d9::D3DFMT_R3G3B2: + tformat.dwFlags = DDPF_RGB; + tformat.dwRGBBitCount = 8; + tformat.dwRGBAlphaBitMask = 0x00; + tformat.dwRBitMask = 0xe0; + tformat.dwGBitMask = 0x1c; + tformat.dwBBitMask = 0x03; + break; + + case d3d9::D3DFMT_P8: + tformat.dwFlags = DDPF_RGB | DDPF_PALETTEINDEXED8; + tformat.dwRGBBitCount = 8; + break; + + case d3d9::D3DFMT_L8: + tformat.dwFlags = DDPF_LUMINANCE; + tformat.dwLuminanceBitCount = 8; + tformat.dwLuminanceBitMask = 0xff; + break; + + case d3d9::D3DFMT_A8L8: + tformat.dwFlags = DDPF_ALPHAPIXELS | DDPF_LUMINANCE; + tformat.dwLuminanceBitCount = 16; + tformat.dwLuminanceAlphaBitMask = 0xff00; + tformat.dwLuminanceBitMask = 0x00ff; + break; + + case d3d9::D3DFMT_A4L4: + tformat.dwFlags = DDPF_ALPHAPIXELS | DDPF_LUMINANCE; + tformat.dwLuminanceAlphaBitMask = 0xf0; + tformat.dwLuminanceBitCount = 8; + tformat.dwLuminanceBitMask = 0x0f; + break; + + case d3d9::D3DFMT_V8U8: + tformat.dwFlags = DDPF_BUMPDUDV; + tformat.dwBumpBitCount = 16; + tformat.dwBumpDvBitMask = 0xff00; + tformat.dwBumpDuBitMask = 0x00ff; + break; + + case d3d9::D3DFMT_L6V5U5: + tformat.dwFlags = DDPF_BUMPDUDV | DDPF_BUMPLUMINANCE; + tformat.dwBumpBitCount = 16; + tformat.dwBumpLuminanceBitMask = 0xfc00; + tformat.dwBumpDvBitMask = 0x03e0; + tformat.dwBumpDuBitMask = 0x001f; + break; + + case d3d9::D3DFMT_X8L8V8U8: + tformat.dwFlags = DDPF_BUMPDUDV | DDPF_BUMPLUMINANCE; + tformat.dwBumpBitCount = 32; + tformat.dwLuminanceAlphaBitMask = 0x00000000; + tformat.dwBumpLuminanceBitMask = 0x00ff0000; + tformat.dwBumpDvBitMask = 0x0000ff00; + tformat.dwBumpDuBitMask = 0x000000ff; + break; + + case d3d9::D3DFMT_DXT1: + tformat.dwFlags = DDPF_FOURCC; + tformat.dwFourCC = MAKEFOURCC('D', 'X', 'T', '1'); + break; + + case d3d9::D3DFMT_DXT2: + tformat.dwFlags = DDPF_FOURCC; + tformat.dwFourCC = MAKEFOURCC('D', 'X', 'T', '2'); + break; + + case d3d9::D3DFMT_DXT3: + tformat.dwFlags = DDPF_FOURCC; + tformat.dwFourCC = MAKEFOURCC('D', 'X', 'T', '3'); + break; + + case d3d9::D3DFMT_DXT4: + tformat.dwFlags = DDPF_FOURCC; + tformat.dwFourCC = MAKEFOURCC('D', 'X', 'T', '4'); + break; + + case d3d9::D3DFMT_DXT5: + tformat.dwFlags = DDPF_FOURCC; + tformat.dwFourCC = MAKEFOURCC('D', 'X', 'T', '5'); + break; + + default: + case d3d9::D3DFMT_UNKNOWN: + Logger::err("GetTextureFormat: Unhandled format"); + break; + } + + return tformat; + } + + inline DDPIXELFORMAT GetZBufferFormat (d3d9::D3DFORMAT format) { + DDPIXELFORMAT zformat = { }; + zformat.dwSize = sizeof(DDPIXELFORMAT); + + switch (format) { + case d3d9::D3DFMT_D15S1: + zformat.dwFlags = DDPF_ZBUFFER | DDPF_STENCILBUFFER; + zformat.dwZBufferBitDepth = 16; + zformat.dwZBitMask = 0x7fff; + zformat.dwStencilBitDepth = 1; + zformat.dwStencilBitMask = 0x8000; + break; + + case d3d9::D3DFMT_D16: + zformat.dwFlags = DDPF_ZBUFFER; + zformat.dwZBufferBitDepth = 16; + zformat.dwZBitMask = 0xffff; + zformat.dwStencilBitDepth = 0; + zformat.dwStencilBitMask = 0x0000; + break; + + case d3d9::D3DFMT_D24X4S4: + zformat.dwFlags = DDPF_ZBUFFER | DDPF_STENCILBUFFER; + zformat.dwZBufferBitDepth = 32; + zformat.dwZBitMask = 0x00ffffff; + zformat.dwStencilBitDepth = 4; + zformat.dwStencilBitMask = 0xf0000000; + break; + + case d3d9::D3DFMT_D24X8: + zformat.dwFlags = DDPF_ZBUFFER; + zformat.dwZBufferBitDepth = 32; + zformat.dwZBitMask = 0x00ffffff; + zformat.dwStencilBitDepth = 0; + zformat.dwStencilBitMask = 0x00000000; + break; + + case d3d9::D3DFMT_D24S8: + zformat.dwFlags = DDPF_ZBUFFER | DDPF_STENCILBUFFER; + zformat.dwZBufferBitDepth = 32; + zformat.dwZBitMask = 0x00ffffff; + zformat.dwStencilBitDepth = 8; + zformat.dwStencilBitMask = 0xff000000; + break; + + case d3d9::D3DFMT_D32: + zformat.dwFlags = DDPF_ZBUFFER; + zformat.dwZBufferBitDepth = 32; + zformat.dwZBitMask = 0xffffffff; + zformat.dwStencilBitDepth = 0; + zformat.dwStencilBitMask = 0x00000000; + break; + + default: + case d3d9::D3DFMT_UNKNOWN: + Logger::err("GetZBufferFormat: Unhandled format"); + break; + } + + return zformat; + } + + inline bool IsDXTFormat(d3d9::D3DFORMAT fmt) { + return fmt == d3d9::D3DFMT_DXT1 + || fmt == d3d9::D3DFMT_DXT2 + || fmt == d3d9::D3DFMT_DXT3 + || fmt == d3d9::D3DFMT_DXT4 + || fmt == d3d9::D3DFMT_DXT5; + } + + template + inline HRESULT ValidateSurfaceFlags(DescType* desc) { + const bool isOffScreenPlainSurface = desc->ddsCaps.dwCaps & DDSCAPS_OFFSCREENPLAIN; + const bool isTexture = desc->ddsCaps.dwCaps & DDSCAPS_TEXTURE; + const bool isInVideoMemory = desc->ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY; + const bool isInSystemMemory = desc->ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY; + const bool isWriteOnly = desc->ddsCaps.dwCaps & DDSCAPS_WRITEONLY; + + if (unlikely(isOffScreenPlainSurface && (isInVideoMemory || isInSystemMemory))) { + return isWriteOnly ? DDERR_INVALIDCAPS : DD_OK; + } + + if (unlikely(isTexture && (isInVideoMemory || isInSystemMemory))) { + return isWriteOnly ? DDERR_INVALIDCAPS : DD_OK; + } + + return DD_OK; + } + + // D3D5 Callback function used to navigate a flipable surface swapchain + inline HRESULT STDMETHODCALLTYPE ListBackBufferSurfacesCallback(IDirectDrawSurface* subsurf, DDSURFACEDESC* desc, void* ctx) { + IDirectDrawSurface** nextBackBuffer = static_cast(ctx); + + if (desc->ddsCaps.dwCaps & DDSCAPS_FLIP) { + *nextBackBuffer = subsurf; + return DDENUMRET_CANCEL; + } + + return DDENUMRET_OK; + } + + // D3D6 Callback function used to navigate a flipable surface swapchain + inline HRESULT STDMETHODCALLTYPE ListBackBufferSurfaces4Callback(IDirectDrawSurface4* subsurf, DDSURFACEDESC2* desc, void* ctx) { + IDirectDrawSurface4** nextBackBuffer = static_cast(ctx); + + if (desc->ddsCaps.dwCaps & DDSCAPS_FLIP) { + *nextBackBuffer = subsurf; + return DDENUMRET_CANCEL; + } + + return DDENUMRET_OK; + } + + // D3D7 callback function used to navigate a flipable surface swapchain + inline HRESULT STDMETHODCALLTYPE ListBackBufferSurfaces7Callback(IDirectDrawSurface7* subsurf, DDSURFACEDESC2* desc, void* ctx) { + IDirectDrawSurface7** nextBackBuffer = static_cast(ctx); + + if (desc->ddsCaps.dwCaps & DDSCAPS_FLIP) { + *nextBackBuffer = subsurf; + return DDENUMRET_CANCEL; + } + + return DDENUMRET_OK; + } + + // D3D5 callback function used to navigate the linked mip map chain + inline HRESULT STDMETHODCALLTYPE ListMipChainSurfacesCallback(IDirectDrawSurface* subsurf, DDSURFACEDESC* desc, void* ctx) { + IDirectDrawSurface** nextMip = static_cast(ctx); + + if (desc->ddsCaps.dwCaps & DDSCAPS_MIPMAP) { + *nextMip = subsurf; + return DDENUMRET_CANCEL; + } + + return DDENUMRET_OK; + } + + // D3D6 callback function used to navigate the linked mip map chain + inline HRESULT STDMETHODCALLTYPE ListMipChainSurfaces4Callback(IDirectDrawSurface4* subsurf, DDSURFACEDESC2* desc, void* ctx) { + IDirectDrawSurface4** nextMip = static_cast(ctx); + + if ((desc->ddsCaps.dwCaps & DDSCAPS_MIPMAP) + || (desc->ddsCaps.dwCaps2 & DDSCAPS2_MIPMAPSUBLEVEL)) { + *nextMip = subsurf; + return DDENUMRET_CANCEL; + } + + return DDENUMRET_OK; + } + + // D3D7 callback function used to navigate the linked mip map chain + inline HRESULT STDMETHODCALLTYPE ListMipChainSurfaces7Callback(IDirectDrawSurface7* subsurf, DDSURFACEDESC2* desc, void* ctx) { + IDirectDrawSurface7** nextMip = static_cast(ctx); + + if ((desc->ddsCaps.dwCaps & DDSCAPS_MIPMAP) + || (desc->ddsCaps.dwCaps2 & DDSCAPS2_MIPMAPSUBLEVEL)) { + *nextMip = subsurf; + return DDENUMRET_CANCEL; + } + + return DDENUMRET_OK; + } + + // D3D5 callback function used to enumerate attached surfaces + inline HRESULT STDMETHODCALLTYPE EnumAttachedSurfacesCallback(IDirectDrawSurface* surface, DDSURFACEDESC* desc, void* ctx) { + auto& attachedSurfaces = *static_cast*>(ctx); + + AttachedSurface attachedSurface = { surface, *desc }; + attachedSurfaces.push_back(attachedSurface); + + return DDENUMRET_OK; + } + + // D3D6 callback function used to enumerate attached surfaces + inline HRESULT STDMETHODCALLTYPE EnumAttachedSurfaces4Callback(IDirectDrawSurface4* surface, DDSURFACEDESC2* desc, void* ctx) { + auto& attachedSurfaces = *static_cast*>(ctx); + + AttachedSurface4 attachedSurface4 = { surface, *desc }; + attachedSurfaces.push_back(attachedSurface4); + + return DDENUMRET_OK; + } + + // D3D7 callback function used to enumerate attached surfaces + inline HRESULT STDMETHODCALLTYPE EnumAttachedSurfaces7Callback(IDirectDrawSurface7* surface, DDSURFACEDESC2* desc, void* ctx) { + auto& attachedSurfaces = *static_cast*>(ctx); + + AttachedSurface7 attachedSurface7 = { surface, *desc }; + attachedSurfaces.push_back(attachedSurface7); + + return DDENUMRET_OK; + } + + // D3D7 callback function used in cube map face/surface initialization + inline HRESULT STDMETHODCALLTYPE EnumAndAttachCubeMapFacesCallback(IDirectDrawSurface7* subsurf, DDSURFACEDESC2* desc, void* ctx) { + CubeMapAttachedSurfaces* cubeMapAttachedSurfaces = static_cast(ctx); + + // Skip any surface which isn't a cube map face + if (unlikely(!IsCubeMapFace(desc))) + return DDENUMRET_OK; + + if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX) { + cubeMapAttachedSurfaces->positiveX = subsurf; + return DDENUMRET_OK; + } else if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX) { + cubeMapAttachedSurfaces->negativeX = subsurf; + return DDENUMRET_OK; + } else if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY) { + cubeMapAttachedSurfaces->positiveY = subsurf; + return DDENUMRET_OK; + } else if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY) { + cubeMapAttachedSurfaces->negativeY = subsurf; + return DDENUMRET_OK; + } else if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ) { + cubeMapAttachedSurfaces->positiveZ = subsurf; + return DDENUMRET_OK; + } else if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ) { + cubeMapAttachedSurfaces->negativeZ = subsurf; + return DDENUMRET_OK; + } + + return DDENUMRET_OK; + } + + inline void BlitToD3D9CubeMap( + d3d9::IDirect3DCubeTexture9* cubeTex9, + d3d9::D3DFORMAT format9, + IDirectDrawSurface7* surface, + uint16_t mipLevels) { + // Properly handle cube textures with auto-generated mip maps + const uint16_t actualMipLevels = std::max(1u, mipLevels); + + DDSURFACEDESC2 desc = { }; + desc.dwSize = sizeof(DDSURFACEDESC2); + surface->GetSurfaceDesc(&desc); + const d3d9::D3DCUBEMAP_FACES face = GetCubemapFace(&desc); + + Logger::debug(str::format("BlitToD3D9CubeMap: Blitting face ", face)); + + IDirectDrawSurface7* mipMap = surface; + + Logger::debug(str::format("BlitToD3D9CubeMap: Blitting ", actualMipLevels, " mip map(s)")); + + for (uint16_t i = 0; i < actualMipLevels; i++) { + // Should never occur normally, but acts as a last ditch safety check + if (unlikely(mipMap == nullptr)) { + Logger::warn(str::format("BlitToD3D9CubeMap: Last found source mip ", i - 1)); + break; + } + + d3d9::D3DLOCKED_RECT rect9mip; + // D3DLOCK_DISCARD will get ignored for MANAGED/SYSTEMMEM, but will work on DEFAULT + HRESULT hr9 = cubeTex9->LockRect(face, i, &rect9mip, 0, D3DLOCK_DISCARD); + if (likely(SUCCEEDED(hr9))) { + DDSURFACEDESC2 descMip; + descMip.dwSize = sizeof(DDSURFACEDESC2); + HRESULT hr = mipMap->Lock(0, &descMip, DDLOCK_READONLY, 0); + if (likely(SUCCEEDED(hr))) { + Logger::debug(str::format("descMip.dwWidth: ", descMip.dwWidth)); + Logger::debug(str::format("descMip.dwHeight: ", descMip.dwHeight)); + Logger::debug(str::format("descMip.lPitch: ", descMip.lPitch)); + Logger::debug(str::format("rect9mip.Pitch: ", rect9mip.Pitch)); + // The lock pitch of a DXT surface represents its entire size, apparently + if (IsDXTFormat(format9)) { + const size_t size = static_cast(descMip.lPitch); + memcpy(rect9mip.pBits, descMip.lpSurface, size); + Logger::debug(str::format("BlitToD3D9CubeMap: Done blitting DXT mip ", i)); + } else if (unlikely(descMip.lPitch != rect9mip.Pitch)) { + Logger::debug(str::format("BlitToD3D9CubeMap: Incompatible mip map ", i, " pitch")); + + uint8_t* data9 = reinterpret_cast(rect9mip.pBits); + uint8_t* data7 = reinterpret_cast(descMip.lpSurface); + + const size_t copyPitch = std::min(descMip.lPitch, rect9mip.Pitch); + for (uint32_t h = 0; h < descMip.dwHeight; h++) + memcpy(&data9[h * rect9mip.Pitch], &data7[h * descMip.lPitch], copyPitch); + + Logger::debug(str::format("BlitToD3D9CubeMap: Done blitting mip ", i, " row by row")); + } else { + const size_t size = static_cast(descMip.dwHeight * descMip.lPitch); + memcpy(rect9mip.pBits, descMip.lpSurface, size); + Logger::debug(str::format("BlitToD3D9CubeMap: Done blitting mip ", i)); + } + mipMap->Unlock(0); + } else { + Logger::warn(str::format("BlitToD3D9CubeMap: Failed to lock mip ", i)); + } + cubeTex9->UnlockRect(face, i); + + IDirectDrawSurface7* parentSurface = mipMap; + mipMap = nullptr; + + parentSurface->EnumAttachedSurfaces(&mipMap, ListMipChainSurfaces7Callback); + } else { + Logger::warn(str::format("BlitToD3D9CubeMap: Failed to lock D3D9 mip ", i)); + } + } + } + + template + inline void BlitToD3D9Texture( + d3d9::IDirect3DTexture9* texture9, + d3d9::D3DFORMAT format9, + SurfaceType* surface, + uint16_t mipLevels) { + // Properly handle textures with auto-generated mip maps + const uint16_t actualMipLevels = std::max(1u, mipLevels); + + SurfaceType* mipMap = surface; + + Logger::debug(str::format("BlitToD3D9Texture: Blitting ", actualMipLevels, " mip map(s)")); + + for (uint16_t i = 0; i < actualMipLevels; i++) { + // Should never occur normally, but acts as a last ditch safety check + if (unlikely(mipMap == nullptr)) { + Logger::warn(str::format("BlitToD3D9Texture: Last found source mip ", i - 1)); + break; + } + + d3d9::D3DLOCKED_RECT rect9mip; + // D3DLOCK_DISCARD will get ignored for MANAGED/SYSTEMMEM, but will work on DEFAULT + HRESULT hr9 = texture9->LockRect(i, &rect9mip, 0, D3DLOCK_DISCARD); + if (likely(SUCCEEDED(hr9))) { + DescType descMip; + descMip.dwSize = sizeof(DescType); + HRESULT hr = mipMap->Lock(0, &descMip, DDLOCK_READONLY, 0); + if (likely(SUCCEEDED(hr))) { + Logger::debug(str::format("descMip.dwWidth: ", descMip.dwWidth)); + Logger::debug(str::format("descMip.dwHeight: ", descMip.dwHeight)); + Logger::debug(str::format("descMip.lPitch: ", descMip.lPitch)); + Logger::debug(str::format("rect9mip.Pitch: ", rect9mip.Pitch)); + // The lock pitch of a DXT surface represents its entire size, apparently + if (IsDXTFormat(format9)) { + const size_t size = static_cast(descMip.lPitch); + memcpy(rect9mip.pBits, descMip.lpSurface, size); + Logger::debug(str::format("BlitToD3D9Texture: Done blitting DXT mip ", i)); + } else if (unlikely(descMip.lPitch != rect9mip.Pitch)) { + Logger::debug(str::format("BlitToD3D9Texture: Incompatible mip map ", i, " pitch")); + + uint8_t* data9 = reinterpret_cast(rect9mip.pBits); + uint8_t* data7 = reinterpret_cast(descMip.lpSurface); + + const size_t copyPitch = std::min(descMip.lPitch, rect9mip.Pitch); + for (uint32_t h = 0; h < descMip.dwHeight; h++) + memcpy(&data9[h * rect9mip.Pitch], &data7[h * descMip.lPitch], copyPitch); + + Logger::debug(str::format("BlitToD3D9Texture: Done blitting mip ", i, " row by row")); + } else { + const size_t size = static_cast(descMip.dwHeight * descMip.lPitch); + memcpy(rect9mip.pBits, descMip.lpSurface, size); + Logger::debug(str::format("BlitToD3D9Texture: Done blitting mip ", i)); + } + mipMap->Unlock(0); + } else { + Logger::warn(str::format("BlitToD3D9Texture: Failed to lock mip ", i)); + } + texture9->UnlockRect(i); + + SurfaceType* parentSurface = mipMap; + mipMap = nullptr; + + if constexpr (std::is_same::value) { + parentSurface->EnumAttachedSurfaces(&mipMap, ListMipChainSurfaces7Callback); + } else if constexpr (std::is_same::value) { + parentSurface->EnumAttachedSurfaces(&mipMap, ListMipChainSurfaces4Callback); + } else if constexpr (std::is_same::value) { + parentSurface->EnumAttachedSurfaces(&mipMap, ListMipChainSurfacesCallback); + } else { + Logger::err("BlitToD3D9Texture: Unsupported surface type"); + } + } else { + Logger::warn(str::format("BlitToD3D9Texture: Failed to lock D3D9 mip ", i)); + } + } + } + + template + inline void BlitToD3D9Surface( + d3d9::IDirect3DSurface9* surface9, + d3d9::D3DFORMAT format9, + SurfaceType* surface) { + d3d9::D3DLOCKED_RECT rect9; + // D3DLOCK_DISCARD will get ignored for MANAGED/SYSTEMMEM, but will work on DEFAULT + HRESULT hr9 = surface9->LockRect(&rect9, 0, D3DLOCK_DISCARD); + if (likely(SUCCEEDED(hr9))) { + DescType desc; + desc.dwSize = sizeof(DescType); + HRESULT hr = surface->Lock(0, &desc, DDLOCK_READONLY, 0); + if (likely(SUCCEEDED(hr))) { + Logger::debug(str::format("desc.dwWidth: ", desc.dwWidth)); + Logger::debug(str::format("desc.dwHeight: ", desc.dwHeight)); + Logger::debug(str::format("desc.lPitch: ", desc.lPitch)); + Logger::debug(str::format("rect.Pitch: ", rect9.Pitch)); + // The lock pitch of a DXT surface represents its entire size, apparently + if (IsDXTFormat(format9)) { + const size_t size = static_cast(desc.lPitch); + memcpy(rect9.pBits, desc.lpSurface, size); + Logger::debug("BlitToD3D9Texture: Done blitting DXT surface"); + } else if (unlikely(desc.lPitch != rect9.Pitch)) { + Logger::debug("BlitToD3D9Surface: Incompatible surface pitch"); + + uint8_t* data9 = reinterpret_cast(rect9.pBits); + uint8_t* data7 = reinterpret_cast(desc.lpSurface); + + const size_t copyPitch = std::min(desc.lPitch, rect9.Pitch); + for (uint32_t h = 0; h < desc.dwHeight; h++) + memcpy(&data9[h * rect9.Pitch], &data7[h * desc.lPitch], copyPitch); + + Logger::debug("BlitToD3D9Surface: Done blitting surface row by row"); + } else { + const size_t size = static_cast(desc.dwHeight * desc.lPitch); + memcpy(rect9.pBits, desc.lpSurface, size); + Logger::debug("BlitToD3D9Surface: Done blitting surface"); + } + surface->Unlock(0); + } else { + Logger::warn("BlitToD3D9Surface: Failed to lock surface"); + } + surface9->UnlockRect(); + } else { + Logger::warn("BlitToD3D9Surface: Failed to lock D3D9 surface"); + } + } + + // reverse blitter, used in the forceProxiedPresent logic + template + inline void BlitToDDrawSurface( + SurfaceType* surface, + d3d9::IDirect3DSurface9* surface9) { + DescType desc; + desc.dwSize = sizeof(DescType); + HRESULT hr = surface->Lock(0, &desc, DDLOCK_WRITEONLY, 0); + if (likely(SUCCEEDED(hr))) { + d3d9::D3DLOCKED_RECT rect9; + HRESULT hr9 = surface9->LockRect(&rect9, 0, D3DLOCK_READONLY); + if (likely(SUCCEEDED(hr9))) { + Logger::debug(str::format("desc.dwWidth: ", desc.dwWidth)); + Logger::debug(str::format("desc.dwHeight: ", desc.dwHeight)); + Logger::debug(str::format("desc.lPitch: ", desc.lPitch)); + Logger::debug(str::format("rect.Pitch: ", rect9.Pitch)); + if (unlikely(desc.lPitch != rect9.Pitch)) { + Logger::debug("BlitToDDrawSurface: Incompatible surface pitch"); + + uint8_t* data7 = reinterpret_cast(desc.lpSurface); + uint8_t* data9 = reinterpret_cast(rect9.pBits); + + const size_t copyPitch = std::min(desc.lPitch, rect9.Pitch); + for (uint32_t h = 0; h < desc.dwHeight; h++) + memcpy(&data7[h * desc.lPitch], &data9[h * rect9.Pitch], copyPitch); + + Logger::debug("BlitToDDrawSurface: Done blitting surface row by row"); + } else { + const size_t size = static_cast(desc.dwHeight * desc.lPitch); + memcpy(desc.lpSurface, rect9.pBits, size); + Logger::debug("BlitToDDrawSurface: Done blitting surface"); + } + surface9->UnlockRect(); + } else { + Logger::warn("BlitToDDrawSurface: Failed to lock D3D9 surface"); + } + surface->Unlock(0); + } else { + Logger::warn("BlitToDDrawSurface: Failed to lock surface"); + } + } + + inline DDCOLORKEY GetColorChannel(DWORD pixel, DWORD mask) { + uint32_t shift = 0; + DWORD cmask = mask; + while ((cmask & 1) == 0) { + cmask >>= 1; + shift++; + } + + uint32_t bits = 0; + cmask = mask; + while (cmask) { + if (cmask & 1) + bits++; + cmask >>= 1; + } + + DWORD value = (pixel & mask) >> shift; + DWORD max = (1 << bits) - 1; + float cvalue = (float)value * 255.0 / (float)max; + float half = 255.0 / (2.0 * (float)max); + float minRange = cvalue - half; + float maxRange = cvalue + half; + + DDCOLORKEY colorKey = { }; + colorKey.dwColorSpaceLowValue = (DWORD)std::max(0.0f, std::floor(minRange - 0.5f)); + colorKey.dwColorSpaceHighValue = (DWORD)std::min(255.0f, std::ceil(maxRange + 0.5f)); + + return colorKey; + } + + inline DDCOLORKEY ColorKeyToRGB(const DDPIXELFORMAT* fmt, DWORD colorKey) { + DDCOLORKEY rgbColorKey = { }; + + if (unlikely(!(fmt->dwFlags & DDPF_RGB))) + return rgbColorKey; + + DDCOLORKEY r = GetColorChannel(colorKey, fmt->dwRBitMask); + DDCOLORKEY g = GetColorChannel(colorKey, fmt->dwGBitMask); + DDCOLORKEY b = GetColorChannel(colorKey, fmt->dwBBitMask); + + rgbColorKey.dwColorSpaceLowValue = r.dwColorSpaceLowValue | + (g.dwColorSpaceLowValue << 8) | + (b.dwColorSpaceLowValue << 16); + rgbColorKey.dwColorSpaceHighValue = r.dwColorSpaceHighValue | + (g.dwColorSpaceHighValue << 8) | + (b.dwColorSpaceHighValue << 16); + + return rgbColorKey; + } + +} diff --git a/src/ddraw/ddraw_gamma.cpp b/src/ddraw/ddraw_gamma.cpp new file mode 100644 index 00000000000..92a67f71034 --- /dev/null +++ b/src/ddraw/ddraw_gamma.cpp @@ -0,0 +1,104 @@ +#include "ddraw_gamma.h" + +namespace dxvk { + + DDrawGammaControl::DDrawGammaControl( + DDrawCommonSurface* commonSurf, + Com&& proxyGamma, + IUnknown* pParent) + : DDrawWrappedObject(pParent, std::move(proxyGamma), nullptr) + , m_commonSurf ( commonSurf ) { + Logger::debug("DDrawGammaControl: Created a new gamma control interface"); + } + + DDrawGammaControl::~DDrawGammaControl() { + Logger::debug("DDrawGammaControl: A gamma control interface bites the dust"); + } + + HRESULT STDMETHODCALLTYPE DDrawGammaControl::QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDrawGammaControl::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(riid == __uuidof(IUnknown) + || riid == __uuidof(IDirectDrawSurface))) { + Logger::debug("DDrawGammaControl::QueryInterface: Query for IDirectDrawSurface"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface2))) { + Logger::debug("DDrawGammaControl::QueryInterface: Query for IDirectDrawSurface2"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface3))) { + Logger::debug("DDrawGammaControl::QueryInterface: Query for IDirectDrawSurface3"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface4))) { + Logger::debug("DDrawGammaControl::QueryInterface: Query for IDirectDrawSurface4"); + return m_parent->QueryInterface(riid, ppvObject); + } + if (unlikely(riid == __uuidof(IDirectDrawSurface7))) { + Logger::debug("DDrawGammaControl::QueryInterface: Query for IDirectDrawSurface7"); + return m_parent->QueryInterface(riid, ppvObject); + } + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE DDrawGammaControl::GetGammaRamp(DWORD dwFlags, LPDDGAMMARAMP lpRampData) { + Logger::debug(">>> DDrawGammaControl::GetGammaRamp"); + + if (unlikely(lpRampData == nullptr)) + return DDERR_INVALIDPARAMS; + + d3d9::IDirect3DDevice9* d3d9Device = m_commonSurf->GetCommonInterface()->GetD3D9Device(); + // For proxied pesentation we need to rely on ddraw to handle gamma + if (likely(d3d9Device != nullptr && !m_commonSurf->GetCommonInterface()->GetOptions()->forceProxiedPresent)) { + Logger::debug("DDrawGammaControl::GetGammaRamp: Getting gamma ramp via D3D9"); + d3d9::D3DGAMMARAMP rampData = { }; + d3d9Device->GetGammaRamp(0, &rampData); + // Both gamma structs are identical in content/size + memcpy(static_cast(lpRampData), static_cast(&rampData), sizeof(DDGAMMARAMP)); + } else { + Logger::debug("DDrawGammaControl::GetGammaRamp: Getting gamma ramp via DDraw"); + return m_proxy->GetGammaRamp(dwFlags, lpRampData); + } + + return DD_OK; + } + + HRESULT STDMETHODCALLTYPE DDrawGammaControl::SetGammaRamp(DWORD dwFlags, LPDDGAMMARAMP lpRampData) { + Logger::debug(">>> DDrawGammaControl::SetGammaRamp"); + + if (unlikely(lpRampData == nullptr)) + return DDERR_INVALIDPARAMS; + + if (likely(!m_commonSurf->GetCommonInterface()->GetOptions()->ignoreGammaRamp)) { + d3d9::IDirect3DDevice9* d3d9Device = m_commonSurf->GetCommonInterface()->GetD3D9Device(); + // For proxied pesentation we need to rely on ddraw to handle gamma + if (likely(d3d9Device != nullptr && !m_commonSurf->GetCommonInterface()->GetOptions()->forceProxiedPresent)) { + Logger::debug("DDrawGammaControl::SetGammaRamp: Setting gamma ramp via D3D9"); + d3d9Device->SetGammaRamp(0, D3DSGR_NO_CALIBRATION, + reinterpret_cast(lpRampData)); + } else { + Logger::debug("DDrawGammaControl::SetGammaRamp: Setting gamma ramp via DDraw"); + return m_proxy->SetGammaRamp(dwFlags, lpRampData); + } + } else { + Logger::info("DDrawGammaControl::SetGammaRamp: Ignoring application set gamma ramp"); + } + + return DD_OK; + } + +} diff --git a/src/ddraw/ddraw_gamma.h b/src/ddraw/ddraw_gamma.h new file mode 100644 index 00000000000..37ef63541d7 --- /dev/null +++ b/src/ddraw/ddraw_gamma.h @@ -0,0 +1,33 @@ +#pragma once + +#include "ddraw_include.h" +#include "ddraw_wrapped_object.h" + +#include "ddraw_common_surface.h" + +namespace dxvk { + + class DDrawGammaControl final : public DDrawWrappedObject { + + public: + + DDrawGammaControl( + DDrawCommonSurface* commonSurf, + Com&& proxyGamma, + IUnknown* pParent); + + ~DDrawGammaControl(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + + HRESULT STDMETHODCALLTYPE GetGammaRamp(DWORD dwFlags, LPDDGAMMARAMP lpRampData); + + HRESULT STDMETHODCALLTYPE SetGammaRamp(DWORD dwFlags, LPDDGAMMARAMP lpRampData); + + private: + + DDrawCommonSurface* m_commonSurf = nullptr; + + }; + +} diff --git a/src/ddraw/ddraw_include.h b/src/ddraw/ddraw_include.h new file mode 100644 index 00000000000..292f63e0f32 --- /dev/null +++ b/src/ddraw/ddraw_include.h @@ -0,0 +1,284 @@ +#pragma once + +#ifndef _MSC_VER +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0A00 +#endif + +#include +#include + +// Declare __uuidof for DDraw/D3D interfaces +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IDirectDraw, 0x6C14DB80, 0xA733, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60); +__CRT_UUID_DECL(IDirectDraw2, 0xB3A6F3E0, 0x2B43, 0x11CF, 0xA2, 0xDE, 0x00, 0xAA, 0x00, 0xB9, 0x33, 0x56); +__CRT_UUID_DECL(IDirectDraw4, 0x9C59509A, 0x39BD, 0x11D1, 0x8C, 0x4A, 0x00, 0xC0, 0x4F, 0xD9, 0x30, 0xC5); +__CRT_UUID_DECL(IDirectDraw7, 0x15E65EC0, 0x3B9C, 0x11D2, 0xB9, 0x2F, 0x00, 0x60, 0x97, 0x97, 0xEA, 0x5B); +__CRT_UUID_DECL(IDirectDrawSurface, 0x6C14DB81, 0xA733, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60); +__CRT_UUID_DECL(IDirectDrawSurface2, 0x57805885, 0x6EEC, 0x11CF, 0x94, 0x41, 0xA8, 0x23, 0x03, 0xC1, 0x0E, 0x27); +__CRT_UUID_DECL(IDirectDrawSurface3, 0xDA044E00, 0x69B2, 0x11D0, 0xA1, 0xD5, 0x00, 0xAA, 0x00, 0xB8, 0xDF, 0xBB); +__CRT_UUID_DECL(IDirectDrawSurface4, 0x0B2B8630, 0xAD35, 0x11D0, 0x8E, 0xA6, 0x00, 0x60, 0x97, 0x97, 0xEA, 0x5B); +__CRT_UUID_DECL(IDirectDrawSurface7, 0x06675A80, 0x3B9B, 0x11D2, 0xB9, 0x2F, 0x00, 0x60, 0x97, 0x97, 0xEA, 0x5B); +__CRT_UUID_DECL(IDirectDrawPalette, 0x6C14DB84, 0xA733, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60); +__CRT_UUID_DECL(IDirectDrawClipper, 0x6C14DB85, 0xA733, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60); +__CRT_UUID_DECL(IDirectDrawGammaControl, 0x69C11C3E, 0xB46B, 0x11D1, 0xAD, 0x7A, 0x00, 0xC0, 0x4F, 0xC2, 0x9B, 0x4E); +__CRT_UUID_DECL(IDirectDrawColorControl, 0x4B9F0EE0, 0x0D7E, 0x11D0, 0x9B, 0x06, 0x00, 0xA0, 0xC9, 0x03, 0xA3, 0xB8); +__CRT_UUID_DECL(IDirect3D, 0x3BBA0080, 0x2421, 0x11CF, 0xA3, 0x1A, 0x00, 0xAA, 0x00, 0xB9, 0x33, 0x56); +__CRT_UUID_DECL(IDirect3D2, 0x6AAE1EC1, 0x662A, 0x11D0, 0x88, 0x9D, 0x00, 0xAA, 0x00, 0xBB, 0xB7, 0x6A); +__CRT_UUID_DECL(IDirect3D3, 0xBB223240, 0xE72B, 0x11D0, 0xA9, 0xB4, 0x00, 0xAA, 0x00, 0xC0, 0x99, 0x3E); +__CRT_UUID_DECL(IDirect3D7, 0xF5049E77, 0x4861, 0x11D2, 0xA4, 0x07, 0x00, 0xA0, 0xC9, 0x06, 0x29, 0xA8); +__CRT_UUID_DECL(IDirect3DDevice, 0x64108800, 0x957D, 0x11D0, 0x89, 0xAB, 0x00, 0xA0, 0xC9, 0x05, 0x41, 0x29); +__CRT_UUID_DECL(IDirect3DDevice2, 0x93281501, 0x8CF8, 0x11D0, 0x89, 0xAB, 0x00, 0xA0, 0xC9, 0x05, 0x41, 0x29); +__CRT_UUID_DECL(IDirect3DDevice3, 0xB0AB3B60, 0x33D7, 0x11D1, 0xA9, 0x81, 0x00, 0xC0, 0x4F, 0xD7, 0xB1, 0x74); +__CRT_UUID_DECL(IDirect3DDevice7, 0xF5049E79, 0x4861, 0x11D2, 0xA4, 0x07, 0x00, 0xA0, 0xC9, 0x06, 0x29, 0xA8); +__CRT_UUID_DECL(IDirect3DLight, 0x4417C142, 0x33AD, 0x11CF, 0x81, 0x6F, 0x00, 0x00, 0xC0, 0x20, 0x15, 0x6E); +__CRT_UUID_DECL(IDirect3DMaterial, 0x4417C144, 0x33AD, 0x11CF, 0x81, 0x6F, 0x00, 0x00, 0xC0, 0x20, 0x15, 0x6E); +__CRT_UUID_DECL(IDirect3DMaterial2, 0x93281503, 0x8CF8, 0x11D0, 0x89, 0xAB, 0x00, 0xA0, 0xC9, 0x05, 0x41, 0x29); +__CRT_UUID_DECL(IDirect3DMaterial3, 0xCA9C46f4, 0xD3C5, 0x11D1, 0xB7, 0x5A, 0x00, 0x60, 0x08, 0x52, 0xB3, 0x12); +__CRT_UUID_DECL(IDirect3DExecuteBuffer, 0x4417C145, 0x33AD, 0x11Cf, 0x81, 0x6F, 0x00, 0x00, 0xC0, 0x20, 0x15, 0x6E); +__CRT_UUID_DECL(IDirect3DVertexBuffer, 0x7A503555, 0x4A83, 0x11D1, 0xA5, 0xDB, 0x00, 0xA0, 0xC9, 0x03, 0x67, 0xF8); +__CRT_UUID_DECL(IDirect3DVertexBuffer7, 0xF5049E7D, 0x4861, 0x11D2, 0xA4, 0x07, 0x00, 0xA0, 0xC9, 0x06, 0x29, 0xA8); +__CRT_UUID_DECL(IDirect3DTexture, 0x2CDCD9E0, 0x25A0, 0x11CF, 0xA3, 0x1A, 0x00, 0xAA, 0x00, 0xB9, 0x33, 0x56); +__CRT_UUID_DECL(IDirect3DTexture2, 0x93281502, 0x8CF8, 0x11D0, 0x89, 0xAB, 0x00, 0xA0, 0xC9, 0x05, 0x41, 0x29); +__CRT_UUID_DECL(IDirect3DViewport, 0x4417C146, 0x33AD, 0x11CF, 0x81, 0x6F, 0x00, 0x00, 0xC0, 0x20, 0x15, 0x6E); +__CRT_UUID_DECL(IDirect3DViewport2, 0x93281500, 0x8CF8, 0x11D0, 0x89, 0xAB, 0x00, 0xA0, 0xC9, 0x05, 0x41, 0x29); +__CRT_UUID_DECL(IDirect3DViewport3, 0xB0AB3B61, 0x33D7, 0x11D1, 0xA9, 0x81, 0x00, 0xC0, 0x4F, 0xD7, 0xB1, 0x74); +#elif defined(_MSC_VER) +interface DECLSPEC_UUID("6C14DB80-A733-11CE-A521-0020AF0BE560") IDirectDraw; +interface DECLSPEC_UUID("B3A6F3E0-2B43-11CF-A2DE-00AA00B93356") IDirectDraw2; +interface DECLSPEC_UUID("9C59509A-39BD-11D1-8C4A-00C04FD930C5") IDirectDraw4; +interface DECLSPEC_UUID("15E65EC0-3B9C-11D2-B92F-00609797EA5B") IDirectDraw7; +interface DECLSPEC_UUID("6C14DB81-A733-11CE-A521-0020AF0BE560") IDirectDrawSurface; +interface DECLSPEC_UUID("57805885-6EEC-11CF-9441-A82303C10E27") IDirectDrawSurface2; +interface DECLSPEC_UUID("DA044E00-69B2-11D0-A1D5-00AA00B8DFBB") IDirectDrawSurface3; +interface DECLSPEC_UUID("0B2B8630-AD35-11D0-8EA6-00609797EA5B") IDirectDrawSurface4; +interface DECLSPEC_UUID("06675A80-3B9B-11D2-B92F-00609797EA5B") IDirectDrawSurface7; +interface DECLSPEC_UUID("6C14DB84-A733-11CE-A521-0020AF0BE560") IDirectDrawPalette; +interface DECLSPEC_UUID("6C14DB85-A733-11CE-A521-0020AF0BE560") IDirectDrawClipper; +interface DECLSPEC_UUID("69C11C3E-B46B-11D1-AD7A-00C04FC29B4E") IDirectDrawGammaControl; +interface DECLSPEC_UUID("4B9F0EE0-0D7E-11D0-9B06-00A0C903A3B8") IDirectDrawColorControl; +interface DECLSPEC_UUID("3BBA0080-2421-11CF-A31A-00AA00B93356") IDirect3D; +interface DECLSPEC_UUID("6AAE1EC1-662A-11D0-889D-00AA00BBB76A") IDirect3D2; +interface DECLSPEC_UUID("BB223240-E72B-11D0-A9B4-00AA00C0993E") IDirect3D3; +interface DECLSPEC_UUID("F5049E77-4861-11D2-A407-00A0C90629A8") IDirect3D7; +interface DECLSPEC_UUID("64108800-957D-11D0-89AB-00A0C9054129") IDirect3DDevice; +interface DECLSPEC_UUID("93281501-8CF8-11D0-89AB-00A0C9054129") IDirect3DDevice2; +interface DECLSPEC_UUID("B0AB3B60-33D7-11D1-A981-00C04FD7B174") IDirect3DDevice3; +interface DECLSPEC_UUID("F5049E79-4861-11D2-A407-00A0C90629A8") IDirect3DDevice7; +interface DECLSPEC_UUID("4417C142-33AD-11CF-816F-0000C020156E") IDirect3DLight; +interface DECLSPEC_UUID("4417C144-33AD-11CF-816F-0000C020156E") IDirect3DMaterial; +interface DECLSPEC_UUID("93281503-8CF8-11D0-89AB-00A0C90367F8") IDirect3DMaterial2; +interface DECLSPEC_UUID("CA9C46f4-D3C5-11D1-816F-00060852B312") IDirect3DMaterial3; +interface DECLSPEC_UUID("4417C145-33AD-11CF-816F-0000C020156E") IDirect3DExecuteBuffer; +interface DECLSPEC_UUID("7A503555-4A83-11D1-A5DB-00A0C90629A8") IDirect3DVertexBuffer; +interface DECLSPEC_UUID("F5049E7D-4861-11D2-A407-00A0C90629A8") IDirect3DVertexBuffer7; +interface DECLSPEC_UUID("2CDCD9E0-25A0-11CF-A31A-00AA00B93356") IDirect3DTexture; +interface DECLSPEC_UUID("93281502-8CF8-11D0-89AB-00A0C9054129") IDirect3DTexture2; +interface DECLSPEC_UUID("4417C146-33AD-11CF-816F-0000C020156E") IDirect3DViewport; +interface DECLSPEC_UUID("93281500-8CF8-11D0-89AB-00A0C9054129") IDirect3DViewport2; +interface DECLSPEC_UUID("B0AB3B61-33D7-11D1-A981-00C04FD7B174") IDirect3DViewport3; +#endif + +// Undefine D3D 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 +#define D3DCOLORVALUE_DEFINED + +// Temporarily undefine __CRT_UUID_DECL +// to allow imports in the d3d9 namespace +#pragma push_macro("__CRT_UUID_DECL") +#ifdef __CRT_UUID_DECL +#undef __CRT_UUID_DECL +#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") + +// Declare __uuidof for D3D9 interfaces, directly within the d3d9:: namespace +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(d3d9::IDirect3D9, 0x81BDCBCA, 0x64D4, 0x426D, 0xAE, 0x8D, 0xAD, 0x01, 0x47, 0xF4, 0x27, 0x5C); +__CRT_UUID_DECL(d3d9::IDirect3DVolume9, 0x24F416E6, 0x1F67, 0x4AA7, 0xB8, 0x8E, 0xD3, 0x3F, 0x6F, 0x31, 0x28, 0xA1); +__CRT_UUID_DECL(d3d9::IDirect3DSwapChain9, 0x794950F2, 0xADFC, 0x458A, 0x90, 0x5E, 0x10, 0xA1, 0x0B, 0x0B, 0x50, 0x3B); +__CRT_UUID_DECL(d3d9::IDirect3DResource9, 0x05EEC05D, 0x8F7D, 0x4362, 0xB9, 0x99, 0xD1, 0xBA, 0xF3, 0x57, 0xC7, 0x04); +__CRT_UUID_DECL(d3d9::IDirect3DSurface9, 0x0CFBAF3A, 0x9FF6, 0x429A, 0x99, 0xB3, 0xA2, 0x79, 0x6A, 0xF8, 0xB8, 0x9B); +__CRT_UUID_DECL(d3d9::IDirect3DVertexBuffer9, 0xB64BB1B5, 0xFD70, 0x4DF6, 0xBF, 0x91, 0x19, 0xD0, 0xA1, 0x24, 0x55, 0xE3); +__CRT_UUID_DECL(d3d9::IDirect3DIndexBuffer9, 0x7C9DD65E, 0xD3F7, 0x4529, 0xAC, 0xEE, 0x78, 0x58, 0x30, 0xAC, 0xDE, 0x35); +__CRT_UUID_DECL(d3d9::IDirect3DBaseTexture9, 0x580CA87E, 0x1D3C, 0x4D54, 0x99, 0x1D, 0xB7, 0xD3, 0xE3, 0xC2, 0x98, 0xCE); +__CRT_UUID_DECL(d3d9::IDirect3DCubeTexture9, 0xFFF32F81, 0xD953, 0x473A, 0x92, 0x23, 0x93, 0xD6, 0x52, 0xAB, 0xA9, 0x3F); +__CRT_UUID_DECL(d3d9::IDirect3DTexture9, 0x85C31227, 0x3DE5, 0x4F00, 0x9B, 0x3A, 0xF1, 0x1A, 0xC3, 0x8C, 0x18, 0xB5); +__CRT_UUID_DECL(d3d9::IDirect3DVolumeTexture9, 0x2518526C, 0xE789, 0x4111, 0xA7, 0xB9, 0x47, 0xEF, 0x32, 0x8D, 0x13, 0xE6); +__CRT_UUID_DECL(d3d9::IDirect3DVertexDeclaration9, 0xDD13C59C, 0x36FA, 0x4098, 0xA8, 0xFB, 0xC7, 0xED, 0x39, 0xDC, 0x85, 0x46); +__CRT_UUID_DECL(d3d9::IDirect3DVertexShader9, 0xEFC5557E, 0x6265, 0x4613, 0x8A, 0x94, 0x43, 0x85, 0x78, 0x89, 0xEB, 0x36); +__CRT_UUID_DECL(d3d9::IDirect3DPixelShader9, 0x6D3BDBDC, 0x5B02, 0x4415, 0xB8, 0x52, 0xCE, 0x5E, 0x8B, 0xCC, 0xB2, 0x89); +__CRT_UUID_DECL(d3d9::IDirect3DStateBlock9, 0xB07C4FE5, 0x310D, 0x4BA8, 0xA2, 0x3C, 0x4F, 0x0F, 0x20, 0x6F, 0x21, 0x8B); +__CRT_UUID_DECL(d3d9::IDirect3DQuery9, 0xD9771460, 0xA695, 0x4F26, 0xBB, 0xD3, 0x27, 0xB8, 0x40, 0xB5, 0x41, 0xCC); +__CRT_UUID_DECL(d3d9::IDirect3DDevice9, 0xD0223B96, 0xBF7A, 0x43FD, 0x92, 0xBD, 0xA4, 0x3B, 0x0D, 0x82, 0xB9, 0xEB); +__CRT_UUID_DECL(d3d9::IDirect3D9Ex, 0x02177241, 0x69FC, 0x400C, 0x8F, 0xF1, 0x93, 0xA4, 0x4D, 0xF6, 0x86, 0x1D); +__CRT_UUID_DECL(d3d9::IDirect3DSwapChain9Ex, 0x91886CAF, 0x1C3D, 0x4D2E, 0xA0, 0xAB, 0x3E, 0x4C, 0x7D, 0x8D, 0x33, 0x03); +__CRT_UUID_DECL(d3d9::IDirect3DDevice9Ex, 0xB18B10CE, 0x2649, 0x405A, 0x87, 0x0F, 0x95, 0xF7, 0x77, 0xD4, 0x31, 0x3A); +#endif + +#endif // defined(__MINGW32__) || defined(__GNUC__) + + +// 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/sync/sync_recursive.h" + +#include "../util/util_error.h" +#include "../util/util_likely.h" +#include "../util/util_string.h" + + +// redefine needed D3D macros +#undef D3DFVF_POSITION_MASK +#define D3DFVF_POSITION_MASK 0x00E + +// defined in MinGW headers, but not in MSVC headers... +#ifndef D3DLIGHTCAPS_PARALLELPOINT +#define D3DLIGHTCAPS_PARALLELPOINT 0x00000008 +#endif + +#ifndef D3DLIGHTCAPS_GLSPOT +#define D3DLIGHTCAPS_GLSPOT 0x00000010 +#endif + +#ifndef D3DLIGHT_GLSPOT +#define D3DLIGHT_GLSPOT D3DLIGHTTYPE(5) +#endif + +// This render state got moved in D3D6 from 42 to 27 (replacing D3DRENDERSTATE_BLENDENABLE) +#define D3DRENDERSTATE_ALPHABLENDENABLE_OLD D3DRENDERSTATETYPE(42) + +// D3DDEVICEDESC actually has 3 versions, one used in D3D2/3, +// another in D3D5, and the last in D3D6. Headers only typically +// contain the last, which is a problem because this struct +// has a size that needs to be validated... and all versions are +// expected to be valid options. + +// This is the D3DDEVICEDESC shipped with D3D5 +typedef struct _D3DDeviceDesc2 { + DWORD dwSize; + DWORD dwFlags; + D3DCOLORMODEL dcmColorModel; + DWORD dwDevCaps; + D3DTRANSFORMCAPS dtcTransformCaps; + BOOL bClipping; + D3DLIGHTINGCAPS dlcLightingCaps; + D3DPRIMCAPS dpcLineCaps; + D3DPRIMCAPS dpcTriCaps; + DWORD dwDeviceRenderBitDepth; + DWORD dwDeviceZBufferBitDepth; + DWORD dwMaxBufferSize; + DWORD dwMaxVertexCount; + + DWORD dwMinTextureWidth,dwMinTextureHeight; + DWORD dwMaxTextureWidth,dwMaxTextureHeight; + DWORD dwMinStippleWidth,dwMaxStippleWidth; + DWORD dwMinStippleHeight,dwMaxStippleHeight; +} D3DDEVICEDESC2; + +// This is the D3DDEVICEDESC shipped with D3D2/3 +typedef struct _D3DDeviceDesc3 { + DWORD dwSize; + DWORD dwFlags; + D3DCOLORMODEL dcmColorModel; + DWORD dwDevCaps; + D3DTRANSFORMCAPS dtcTransformCaps; + BOOL bClipping; + D3DLIGHTINGCAPS dlcLightingCaps; + D3DPRIMCAPS dpcLineCaps; + D3DPRIMCAPS dpcTriCaps; + DWORD dwDeviceRenderBitDepth; + DWORD dwDeviceZBufferBitDepth; + DWORD dwMaxBufferSize; + DWORD dwMaxVertexCount; +} D3DDEVICEDESC3; + +// Because of the above mess, D3DFINDDEVICERESULT is also affected in D3D5 +typedef struct _D3DFindDeviceResult2 +{ + DWORD dwSize; + GUID guid; + D3DDEVICEDESC2 ddHwDesc; + D3DDEVICEDESC2 ddSwDesc; +} D3DFINDDEVICERESULT2; + +// Because of the above mess, D3DFINDDEVICERESULT is also affected in D3D3 +typedef struct _D3DFindDeviceResult3 +{ + DWORD dwSize; + GUID guid; + D3DDEVICEDESC3 ddHwDesc; + D3DDEVICEDESC3 ddSwDesc; +} D3DFINDDEVICERESULT3; + +namespace dxvk { + + // Some applications use CLSIDs as an entry point with DllGetClassObject and IClassFactory + static constexpr IID CLSID_DirectDraw = { 0xD7B70EE0, 0x4340, 0x11CF, {0xB0, 0x63, 0x00, 0x20, 0xAF, 0xC2, 0xCD, 0x35} }; + static constexpr IID CLSID_DirectDraw7 = { 0x3C305196, 0x50DB, 0x11D3, {0x9C, 0xFE, 0x00, 0xC0, 0x4F, 0xD9, 0x30, 0xC5} }; + static constexpr IID CLSID_DirectDrawClipper = { 0x593817A0, 0x7DB3, 0x11CF, {0xA2, 0xDE, 0x00, 0xAA, 0x00, 0xB9, 0x33, 0x56} }; + + static constexpr IID IID_IDirect3DTnLHalDevice = { 0xF5049E78, 0x4861, 0x11D2, {0xA4, 0x07, 0x00, 0xA0, 0xC9, 0x06, 0x29, 0xA8} }; + static constexpr IID IID_IDirect3DHALDevice = { 0x84E63DE0, 0x46AA, 0x11CF, {0x81, 0x6F, 0x00, 0x00, 0xC0, 0x20, 0x15, 0x6E} }; + static constexpr IID IID_IDirect3DMMXDevice = { 0x881949A1, 0xD6F3, 0x11D0, {0x89, 0xAB, 0x00, 0xA0, 0xC9, 0x05, 0x41, 0x29} }; + static constexpr IID IID_IDirect3DRGBDevice = { 0xA4665C60, 0x2673, 0x11CF, {0xA3, 0x1A, 0x00, 0xAA, 0x00, 0xB9, 0x33, 0x56} }; + static constexpr IID IID_IDirect3DRampDevice = { 0xF2086B20, 0x259F, 0x11CF, {0xA3, 0x1A, 0x00, 0xAA, 0x00, 0xB9, 0x33, 0x56} }; + static constexpr IID IID_WineD3DDevice = { 0xAEF72D43, 0xB09a, 0x4B7B, {0xB7, 0x98, 0xC6, 0x8A, 0x77, 0x2D, 0x72, 0x2A} }; + + static constexpr GUID GUID_IMediaStream = { 0xB502D1BD, 0x9A57, 0x11D0, {0x8F, 0xDE, 0x00, 0xC0, 0x4F, 0xD9, 0x18, 0x9D} }; + static constexpr GUID GUID_IAMMediaStream = { 0xBEBE595D, 0x9A6F, 0x11D0, {0x8F, 0xDE, 0x00, 0xC0, 0x4F, 0xD9, 0x18, 0x9D} }; + + struct DDrawModeSize { + DWORD width; + DWORD height; + }; + +} diff --git a/src/ddraw/ddraw_main.cpp b/src/ddraw/ddraw_main.cpp new file mode 100644 index 00000000000..976b184c9ff --- /dev/null +++ b/src/ddraw/ddraw_main.cpp @@ -0,0 +1,567 @@ +#include "ddraw_include.h" + +#include "ddraw_class_factory.h" + +#include "ddraw_clipper.h" +#include "ddraw/ddraw_interface.h" +#include "ddraw7/ddraw7_interface.h" + +namespace dxvk { + + Logger Logger::s_instance("ddraw.log"); + + HMODULE GetProxiedDDrawModule() { + static HMODULE hDDraw = nullptr; + + if (unlikely(hDDraw == nullptr)) { + // Try to load ddraw_.dll from the current path first + hDDraw = ::LoadLibraryA("ddraw_.dll"); + + if (hDDraw == nullptr) { + char loadPath[MAX_PATH] = { }; + UINT returnLength = ::GetSystemDirectoryA(loadPath, MAX_PATH); + if (unlikely(!returnLength)) + return nullptr; + + strcat(loadPath, "\\ddraw.dll"); + hDDraw = ::LoadLibraryA(loadPath); + + if (likely(hDDraw != nullptr)) + Logger::debug(">>> GetProxiedDDrawModule: Loaded ddraw.dll from system path"); + } else { + Logger::debug(">>> GetProxiedDDrawModule: Loaded ddraw_.dll"); + } + } + + return hDDraw; + } + + HRESULT CreateDirectDrawEx(GUID *lpGUID, LPVOID *lplpDD, REFIID iid, IUnknown *pUnkOuter) { + if (unlikely(lplpDD == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDD); + + if (unlikely(iid != __uuidof(IDirectDraw7))) + return DDERR_INVALIDPARAMS; + + typedef HRESULT (__stdcall *DirectDrawCreateEx_t)(GUID *lpGUID, LPVOID *lplpDD, REFIID iid, IUnknown *pUnkOuter); + static DirectDrawCreateEx_t ProxiedDirectDrawCreateEx = nullptr; + + try { + if (unlikely(ProxiedDirectDrawCreateEx == nullptr)) { + HMODULE hDDraw = GetProxiedDDrawModule(); + + if (unlikely(!hDDraw)) { + Logger::err("CreateDirectDrawEx: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedDirectDrawCreateEx = reinterpret_cast(::GetProcAddress(hDDraw, "DirectDrawCreateEx")); + + if (unlikely(ProxiedDirectDrawCreateEx == nullptr)) { + Logger::err("CreateDirectDrawEx: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + LPVOID lplpDDProxied = nullptr; + HRESULT hr = ProxiedDirectDrawCreateEx(lpGUID, &lplpDDProxied, iid, pUnkOuter); + + if (unlikely(FAILED(hr))) { + Logger::warn("CreateDirectDrawEx: Failed call to proxied interface"); + return hr; + } + + Com DDraw7IntfProxied = static_cast(lplpDDProxied); + *lplpDD = ref(new DDraw7Interface(nullptr, std::move(DDraw7IntfProxied))); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + + return S_OK; + } + + HRESULT CreateDirectDraw(GUID *lpGUID, LPDIRECTDRAW *lplpDD, IUnknown *pUnkOuter) { + if (unlikely(lplpDD == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDD); + + typedef HRESULT (__stdcall *DirectDrawCreate_t)(GUID *lpGUID, LPVOID *lplpDD, IUnknown *pUnkOuter); + static DirectDrawCreate_t ProxiedDirectDrawCreate = nullptr; + + try { + if (unlikely(ProxiedDirectDrawCreate == nullptr)) { + HMODULE hDDraw = GetProxiedDDrawModule(); + + if (unlikely(!hDDraw)) { + Logger::err("CreateDirectDraw: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedDirectDrawCreate = reinterpret_cast(::GetProcAddress(hDDraw, "DirectDrawCreate")); + + if (unlikely(ProxiedDirectDrawCreate == nullptr)) { + Logger::err("CreateDirectDraw: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + LPVOID lplpDDProxied = nullptr; + HRESULT hr = ProxiedDirectDrawCreate(lpGUID, &lplpDDProxied, pUnkOuter); + + if (unlikely(FAILED(hr))) { + Logger::warn("CreateDirectDraw: Failed call to proxied interface"); + return hr; + } + + Com DDrawIntfProxied = static_cast(lplpDDProxied); + *lplpDD = ref(new DDrawInterface(nullptr, std::move(DDrawIntfProxied))); + } catch (const DxvkError& e) { + Logger::err(e.message()); + return DDERR_GENERIC; + } + + return S_OK; + } + + HRESULT CreateDirectDrawClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) { + if (unlikely(lplpDDClipper == nullptr)) + return DDERR_INVALIDPARAMS; + + InitReturnPtr(lplpDDClipper); + + typedef HRESULT (__stdcall *DirectDrawCreateClipper_t)(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter); + static DirectDrawCreateClipper_t ProxiedDirectDrawCreateClipper = nullptr; + + if (unlikely(ProxiedDirectDrawCreateClipper == nullptr)) { + HMODULE hDDraw = GetProxiedDDrawModule(); + + if (unlikely(hDDraw == nullptr)) { + Logger::err("DirectDrawCreateClipper: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedDirectDrawCreateClipper = reinterpret_cast(GetProcAddress(hDDraw, "DirectDrawCreateClipper")); + + if (unlikely(ProxiedDirectDrawCreateClipper == nullptr)) { + Logger::err("DirectDrawCreateClipper: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + Com lplpDDClipperProxy; + HRESULT hr = ProxiedDirectDrawCreateClipper(dwFlags, &lplpDDClipperProxy, pUnkOuter); + + if (unlikely(FAILED(hr))) { + Logger::warn("DirectDrawCreateClipper: Failed call to proxied interface"); + return hr; + } + + *lplpDDClipper = ref(new DDrawClipper(std::move(lplpDDClipperProxy), nullptr)); + + return S_OK; + } + + HRESULT ClassFactoryCreateDirectDraw(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) { + Logger::debug(">>> ClassFactoryCreateDirectDraw"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(pUnkOuter != nullptr)) + return CLASS_E_NOAGGREGATION; + + IDirectDraw* ppvObjectProxy = nullptr; + HRESULT hr = CreateDirectDraw(NULL, &ppvObjectProxy, NULL); + if (unlikely(FAILED(hr))) + return hr; + + // ClassFactoryCreateDirectDraw can be used to construct objects + // ranging from IDirectDraw to IDirectDraw2 and IDirectDraw4 + if (riid == __uuidof(IDirectDraw)) { + Logger::debug(">>> ClassFactoryCreateDirectDraw: Returning IDirectDraw"); + *ppvObject = static_cast(ppvObjectProxy); + } else if (riid == __uuidof(IDirectDraw2)) { + void* directDraw2 = nullptr; + hr = ppvObjectProxy->QueryInterface(__uuidof(IDirectDraw2), &directDraw2); + if (unlikely(FAILED(hr))) + return hr; + Logger::debug(">>> ClassFactoryCreateDirectDraw: Returning IDirectDraw2"); + *ppvObject = directDraw2; + ppvObjectProxy->Release(); + } else if (riid == __uuidof(IDirectDraw4)) { + void* directDraw4 = nullptr; + hr = ppvObjectProxy->QueryInterface(__uuidof(IDirectDraw4), &directDraw4); + if (unlikely(FAILED(hr))) + return hr; + Logger::debug(">>> ClassFactoryCreateDirectDraw: Returning IDirectDraw4"); + *ppvObject = directDraw4; + ppvObjectProxy->Release(); + // Apparently this also works, but I doubt is ever used in practice, + // since IDirectDraw7 can be requested directly in DllGetClassObject + } else if (riid == __uuidof(IDirectDraw7)) { + Logger::debug(">>> ClassFactoryCreateDirectDraw: Returning IDirectDraw7"); + ppvObjectProxy->Release(); + return CreateDirectDrawEx(NULL, ppvObject, riid, NULL); + } else { + Logger::warn(str::format(">>> ClassFactoryCreateDirectDraw: Unknown IID: ", riid)); + return CLASS_E_CLASSNOTAVAILABLE; + } + + return S_OK; + } + + HRESULT ClassFactoryCreateDirectDrawEx(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) { + Logger::debug(">>> ClassFactoryCreateDirectDrawEx"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(pUnkOuter != nullptr)) + return CLASS_E_NOAGGREGATION; + + if (unlikely(riid != __uuidof(IDirectDraw7))) { + Logger::warn(str::format(">>> ClassFactoryCreateDirectDrawEx: Unknown IID: ", riid)); + return CLASS_E_CLASSNOTAVAILABLE; + } + + Logger::debug(">>> ClassFactoryCreateDirectDrawEx: Returning IDirectDraw7"); + return CreateDirectDrawEx(NULL, ppvObject, riid, NULL); + } + + HRESULT ClassFactoryCreateDirectDrawClipper(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) { + Logger::debug(">>> ClassFactoryCreateDirectDrawClipper"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + if (unlikely(pUnkOuter != nullptr)) + return CLASS_E_NOAGGREGATION; + + if (unlikely(riid != __uuidof(IDirectDrawClipper))) + return CLASS_E_CLASSNOTAVAILABLE; + + return CreateDirectDrawClipper(0, reinterpret_cast(ppvObject), NULL); + } + +} + +extern "C" { + + DLLEXPORT HRESULT __stdcall AcquireDDThreadLock() { + dxvk::Logger::debug("<<< AcquireDDThreadLock: Proxy"); + + typedef HRESULT (__stdcall *AcquireDDThreadLock_t)(); + static AcquireDDThreadLock_t ProxiedAcquireDDThreadLock = nullptr; + + if (unlikely(ProxiedAcquireDDThreadLock == nullptr)) { + HMODULE hDDraw = dxvk::GetProxiedDDrawModule(); + + if (unlikely(hDDraw == nullptr)) { + dxvk::Logger::err("AcquireDDThreadLock: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedAcquireDDThreadLock = reinterpret_cast(GetProcAddress(hDDraw, "AcquireDDThreadLock")); + + if (unlikely(ProxiedAcquireDDThreadLock == nullptr)) { + dxvk::Logger::err("AcquireDDThreadLock: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + return ProxiedAcquireDDThreadLock(); + } + + DLLEXPORT HRESULT __stdcall CompleteCreateSysmemSurface(DWORD arg) { + dxvk::Logger::warn("!!! CompleteCreateSysmemSurface: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall D3DParseUnknownCommand(LPVOID lpCmd, LPVOID *lpRetCmd) { + dxvk::Logger::warn("!!! D3DParseUnknownCommand: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall DDGetAttachedSurfaceLcl(DWORD arg1, DWORD arg2, DWORD arg3) { + dxvk::Logger::warn("!!! DDGetAttachedSurfaceLcl: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall DDInternalLock(DWORD arg1, DWORD arg2) { + dxvk::Logger::warn("!!! DDInternalLock: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall DDInternalUnlock(DWORD arg) { + dxvk::Logger::warn("!!! DDInternalUnlock: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall DSoundHelp(DWORD arg1, DWORD arg2, DWORD arg3) { + dxvk::Logger::warn("!!! DSoundHelp: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall DirectDrawCreate(GUID *lpGUID, LPDIRECTDRAW *lplpDD, IUnknown *pUnkOuter) { + dxvk::Logger::debug(">>> DirectDrawCreate"); + return dxvk::CreateDirectDraw(lpGUID, lplpDD, pUnkOuter); + } + + // Mostly unused, except for Sea Dogs (D3D6) + DLLEXPORT HRESULT __stdcall DirectDrawCreateClipper(DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) { + dxvk::Logger::debug(">>> DirectDrawCreateClipper"); + return dxvk::CreateDirectDrawClipper(dwFlags, lplpDDClipper, pUnkOuter); + } + + DLLEXPORT HRESULT __stdcall DirectDrawCreateEx(GUID *lpGUID, LPVOID *lplpDD, REFIID iid, IUnknown *pUnkOuter) { + dxvk::Logger::debug(">>> DirectDrawCreateEx"); + return dxvk::CreateDirectDrawEx(lpGUID, lplpDD, iid, pUnkOuter); + } + + DLLEXPORT HRESULT __stdcall DirectDrawEnumerateA(LPDDENUMCALLBACKA lpCallback, LPVOID lpContext) { + dxvk::Logger::debug("<<< DirectDrawEnumerateA: Proxy"); + + typedef HRESULT (__stdcall *DirectDrawEnumerateA_t)(LPDDENUMCALLBACKA lpCallback, LPVOID lpContext); + static DirectDrawEnumerateA_t ProxiedDirectDrawEnumerateA = nullptr; + + if (unlikely(ProxiedDirectDrawEnumerateA == nullptr)) { + HMODULE hDDraw = dxvk::GetProxiedDDrawModule(); + + if (unlikely(hDDraw == nullptr)) { + dxvk::Logger::err("DirectDrawEnumerateA: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedDirectDrawEnumerateA = reinterpret_cast(GetProcAddress(hDDraw, "DirectDrawEnumerateA")); + + if (unlikely(ProxiedDirectDrawEnumerateA == nullptr)) { + dxvk::Logger::err("DirectDrawEnumerateA: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + return ProxiedDirectDrawEnumerateA(lpCallback, lpContext); + } + + DLLEXPORT HRESULT __stdcall DirectDrawEnumerateExA(LPDDENUMCALLBACKEXA lpCallback, LPVOID lpContext, DWORD dwFlags) { + dxvk::Logger::debug("<<< DirectDrawEnumerateExA: Proxy"); + + typedef HRESULT (__stdcall *DirectDrawEnumerateExA_t)(LPDDENUMCALLBACKEXA lpCallback, LPVOID lpContext, DWORD dwFlags); + static DirectDrawEnumerateExA_t ProxiedDirectDrawEnumerateExA = nullptr; + + if (unlikely(ProxiedDirectDrawEnumerateExA == nullptr)) { + HMODULE hDDraw = dxvk::GetProxiedDDrawModule(); + + if (unlikely(hDDraw == nullptr)) { + dxvk::Logger::err("DirectDrawEnumerateExA: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedDirectDrawEnumerateExA = reinterpret_cast(GetProcAddress(hDDraw, "DirectDrawEnumerateExA")); + + if (unlikely(ProxiedDirectDrawEnumerateExA == nullptr)) { + dxvk::Logger::err("DirectDrawEnumerateExA: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + return ProxiedDirectDrawEnumerateExA(lpCallback, lpContext, dwFlags); + } + + DLLEXPORT HRESULT __stdcall DirectDrawEnumerateExW(LPDDENUMCALLBACKEXW lpCallback, LPVOID lpContext, DWORD dwFlags) { + dxvk::Logger::debug("<<< DirectDrawEnumerateExW: Proxy"); + + typedef HRESULT (__stdcall *DirectDrawEnumerateExW_t)(LPDDENUMCALLBACKEXW lpCallback, LPVOID lpContext, DWORD dwFlags); + static DirectDrawEnumerateExW_t ProxiedDirectDrawEnumerateExW = nullptr; + + if (unlikely(ProxiedDirectDrawEnumerateExW == nullptr)) { + HMODULE hDDraw = dxvk::GetProxiedDDrawModule(); + + if (unlikely(hDDraw == nullptr)) { + dxvk::Logger::err("DirectDrawEnumerateExW: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedDirectDrawEnumerateExW = reinterpret_cast(GetProcAddress(hDDraw, "DirectDrawEnumerateExW")); + + if (unlikely(ProxiedDirectDrawEnumerateExW == nullptr)) { + dxvk::Logger::err("DirectDrawEnumerateExW: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + return ProxiedDirectDrawEnumerateExW(lpCallback, lpContext, dwFlags); + } + + DLLEXPORT HRESULT __stdcall DirectDrawEnumerateW(LPDDENUMCALLBACKW lpCallback, LPVOID lpContext) { + dxvk::Logger::debug("<<< DirectDrawEnumerateW: Proxy"); + + typedef HRESULT (__stdcall *DirectDrawEnumerateW_t)(LPDDENUMCALLBACKW lpCallback, LPVOID lpContext); + static DirectDrawEnumerateW_t ProxiedDirectDrawEnumerateW = nullptr; + + if (unlikely(ProxiedDirectDrawEnumerateW == nullptr)) { + HMODULE hDDraw = dxvk::GetProxiedDDrawModule(); + + if (unlikely(hDDraw == nullptr)) { + dxvk::Logger::err("DirectDrawEnumerateW: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedDirectDrawEnumerateW = reinterpret_cast(GetProcAddress(hDDraw, "DirectDrawEnumerateW")); + + if (unlikely(ProxiedDirectDrawEnumerateW == nullptr)) { + dxvk::Logger::err("DirectDrawEnumerateW: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + return ProxiedDirectDrawEnumerateW(lpCallback, lpContext); + } + + DLLEXPORT HRESULT __stdcall DllCanUnloadNow() { + dxvk::Logger::debug("<<< DllCanUnloadNow: Proxy"); + + typedef HRESULT (__stdcall *DllCanUnloadNow_t)(); + static DllCanUnloadNow_t ProxiedDllCanUnloadNow = nullptr; + + if (unlikely(ProxiedDllCanUnloadNow == nullptr)) { + HMODULE hDDraw = dxvk::GetProxiedDDrawModule(); + + if (unlikely(hDDraw == nullptr)) { + dxvk::Logger::err("DllCanUnloadNow: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedDllCanUnloadNow = reinterpret_cast(GetProcAddress(hDDraw, "DllCanUnloadNow")); + + if (unlikely(ProxiedDllCanUnloadNow == nullptr)) { + dxvk::Logger::err("DllCanUnloadNow: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + return ProxiedDllCanUnloadNow(); + } + + DLLEXPORT HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) { + dxvk::Logger::debug(">>> DllGetClassObject"); + + dxvk::Logger::debug(dxvk::str::format("DllGetClassObject: Call for rclsid: ", rclsid)); + + if (unlikely(riid != __uuidof(IClassFactory) && riid != __uuidof(IUnknown))) + return E_NOINTERFACE; + + if (likely(rclsid == dxvk::CLSID_DirectDraw)) { + dxvk::DDrawClassFactory* ddrawClassFactory = new dxvk::DDrawClassFactory(dxvk::ClassFactoryCreateDirectDraw); + + *ppv = ref(ddrawClassFactory); + + return S_OK; + } else if (rclsid == dxvk::CLSID_DirectDraw7) { + dxvk::DDrawClassFactory* ddrawClassFactory = new dxvk::DDrawClassFactory(dxvk::ClassFactoryCreateDirectDrawEx); + + *ppv = ref(ddrawClassFactory); + + return S_OK; + } else if (rclsid == dxvk::CLSID_DirectDrawClipper) { + dxvk::DDrawClassFactory* ddrawClassFactory = new dxvk::DDrawClassFactory(dxvk::ClassFactoryCreateDirectDrawClipper); + + *ppv = ref(ddrawClassFactory); + + return S_OK; + } + + return CLASS_E_CLASSNOTAVAILABLE; + } + + DLLEXPORT HRESULT __stdcall GetDDSurfaceLocal(DWORD arg1, DWORD arg2, DWORD arg3) { + dxvk::Logger::warn("!!! GetDDSurfaceLocal: Stub"); + return S_OK; + } + + DLLEXPORT HANDLE __stdcall GetOLEThunkData(DWORD index) { + dxvk::Logger::warn("!!! GetOLEThunkData: Stub"); + return 0; + } + + DLLEXPORT HRESULT __stdcall GetSurfaceFromDC(HDC hdc, LPDIRECTDRAWSURFACE7 *lpDDS, DWORD arg) { + dxvk::Logger::warn("!!! GetSurfaceFromDC: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall InternalLock(DWORD arg1, DWORD arg2) { + dxvk::Logger::warn("!!! InternalLock: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall InternalUnlock(DWORD arg) { + dxvk::Logger::warn("!!! InternalUnlock: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall RegisterSpecialCase(DWORD arg1, DWORD arg2, DWORD arg3, DWORD arg4) { + dxvk::Logger::warn("!!! RegisterSpecialCase: Stub"); + return S_OK; + } + + DLLEXPORT HRESULT __stdcall ReleaseDDThreadLock() { + dxvk::Logger::debug("<<< ReleaseDDThreadLock: Proxy"); + + typedef HRESULT (__stdcall *ReleaseDDThreadLock_t)(); + static ReleaseDDThreadLock_t ProxiedReleaseDDThreadLock = nullptr; + + if (unlikely(ProxiedReleaseDDThreadLock == nullptr)) { + HMODULE hDDraw = dxvk::GetProxiedDDrawModule(); + + if (unlikely(hDDraw == nullptr)) { + dxvk::Logger::err("ReleaseDDThreadLock: Failed to load proxied ddraw.dll"); + return DDERR_GENERIC; + } + + ProxiedReleaseDDThreadLock = reinterpret_cast(GetProcAddress(hDDraw, "AcquireDDThreadLock")); + + if (unlikely(ProxiedReleaseDDThreadLock == nullptr)) { + dxvk::Logger::err("ReleaseDDThreadLock: Failed GetProcAddress"); + return DDERR_GENERIC; + } + } + + return ProxiedReleaseDDThreadLock(); + } + + BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + switch (fdwReason) { + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_ATTACH: + dxvk::Logger::info(">>>>>>> LOADING D7VK >>>>>>>"); + break; + case DLL_PROCESS_DETACH: { + // Calling FreeLibrary on DLL_PROCESS_DETACH is technically discouraged, + // however apitrace appears to do it with no ill effect, and I have no + // other ideas on how to properly free up the proxied ddraw.dll. + HMODULE hDDraw = dxvk::GetProxiedDDrawModule(); + if (likely(hDDraw != nullptr)) + FreeLibrary(hDDraw); + dxvk::Logger::info("<<<<<<< UNLOADING D7VK <<<<<<<"); + break; + } + default: + break; + } + return TRUE; + } + +} diff --git a/src/ddraw/ddraw_options.h b/src/ddraw/ddraw_options.h new file mode 100644 index 00000000000..fd39f1372df --- /dev/null +++ b/src/ddraw/ddraw_options.h @@ -0,0 +1,134 @@ +#pragma once + +#include "ddraw_include.h" + +#include "../util/config/config.h" + +namespace dxvk { + + enum class FSAAEmulation { + Disabled, + Enabled, + Forced + }; + + enum class D3DBackBufferGuard { + Disabled, + Enabled, + Strict + }; + + struct D3DOptions { + + /// Enforces the use of DDSCL_MULTITHREADED + bool forceMultiThreaded; + + /// Use SWVP mode for all D3D9 devices + bool forceSWVP; + + /// Advertise support for R3G3B2 + bool supportR3G3B2; + + /// Advertise support for D16 + bool supportD16; + + /// Replaces any use of D32 with D24X8 + bool useD24X8forD32; + + /// Report any 8-bit display modes as being 16-bit + bool mask8BitModes; + + /// Report POW2 texture dimension restrictions + bool forcePOW2Textures; + + /// Respect DISCARD only on DYNAMIC + WRITEONLY buffers + bool forceLegacyDiscard; + + /// Circumvents the texelFetch color key shader path + bool colorKeyCompatibility; + + /// Map all back buffers onto a single D3D9 back buffer + bool forceSingleBackBuffer; + + /// Resize the back buffer size to screen size when needed + bool backBufferResize; + + /// Write back D3D9 back buffers during blits/locks on DDraw surfaces + bool backBufferWriteBack; + + /// Write back D3D9 depth stencils during blits/locks on DDraw surfaces + bool depthWriteBack; + + /// Upload or skip upload of depth stencils + bool uploadDepthStencils; + + /// Blits back to the proxied flippable surface and presents with DDraw + bool forceProxiedPresent; + + /// Ignore any application set gamma ramp + bool ignoreGammaRamp; + + /// Forces windowed mode presentation in EXCLUSIVE/FULLSCREEN mode + bool ignoreExclusiveMode; + + /// Automatically generate all texture mip maps on the GPU + bool autoGenMipMaps; + + /// Enables all surface write-backs, but comes with performance penalties + bool apitraceMode; + + /// Uses supported MSAA up to 4x to emulate FSAA + FSAAEmulation emulateFSAA; + + /// Determines how uploads for back buffer blits are handled + D3DBackBufferGuard backBufferGuard; + + /// Max available memory override, shared with the D3D9 backend + uint32_t maxAvailableMemory; + + D3DOptions() {} + + D3DOptions(const Config& config) { + // D3D7/6/5/DDraw options + this->forceMultiThreaded = config.getOption ("ddraw.forceMultiThreaded", false); + this->forceSWVP = config.getOption ("ddraw.forceSWVP", false); + this->supportR3G3B2 = config.getOption ("ddraw.supportR3G3B2", false); + this->supportD16 = config.getOption ("ddraw.supportD16", true); + this->useD24X8forD32 = config.getOption ("ddraw.useD24X8forD32", false); + this->mask8BitModes = config.getOption ("ddraw.mask8BitModes", false); + this->forcePOW2Textures = config.getOption ("ddraw.forcePOW2Textures", false); + this->forceLegacyDiscard = config.getOption ("ddraw.forceLegacyDiscard", false); + this->colorKeyCompatibility = config.getOption ("ddraw.colorKeyCompatibility", false); + this->forceSingleBackBuffer = config.getOption ("ddraw.forceSingleBackBuffer", false); + this->backBufferResize = config.getOption ("ddraw.backBufferResize", true); + this->backBufferWriteBack = config.getOption ("ddraw.backBufferWriteBack", false); + this->depthWriteBack = config.getOption ("ddraw.depthWriteBack", false); + this->uploadDepthStencils = config.getOption ("ddraw.uploadDepthStencils", true); + this->forceProxiedPresent = config.getOption ("ddraw.forceProxiedPresent", false); + this->ignoreGammaRamp = config.getOption ("ddraw.ignoreGammaRamp", false); + this->ignoreExclusiveMode = config.getOption ("ddraw.ignoreExclusiveMode", false); + this->autoGenMipMaps = config.getOption ("ddraw.autoGenMipMaps", false); + this->apitraceMode = config.getOption ("ddraw.apitraceMode", false); + + std::string emulateFSAAStr = Config::toLower(config.getOption("ddraw.emulateFSAA", "auto")); + if (emulateFSAAStr == "true") { + this->emulateFSAA = FSAAEmulation::Enabled; + } else if (emulateFSAAStr == "forced") { + this->emulateFSAA = FSAAEmulation::Forced; + } else { + this->emulateFSAA = FSAAEmulation::Disabled; + } + + std::string backBufferGuardStr = Config::toLower(config.getOption("ddraw.backBufferGuard", "auto")); + if (backBufferGuardStr == "strict") { + this->backBufferGuard = D3DBackBufferGuard::Strict; + } else if (backBufferGuardStr == "disabled") { + this->backBufferGuard = D3DBackBufferGuard::Disabled; + } else { + this->backBufferGuard = D3DBackBufferGuard::Enabled; + } + } + + }; + +} diff --git a/src/ddraw/ddraw_palette.cpp b/src/ddraw/ddraw_palette.cpp new file mode 100644 index 00000000000..ed3a1ca0e6d --- /dev/null +++ b/src/ddraw/ddraw_palette.cpp @@ -0,0 +1,54 @@ +#include "ddraw_palette.h" + +#include "ddraw_common_surface.h" + +namespace dxvk { + + uint32_t DDrawPalette::s_paletteCount = 0; + + DDrawPalette::DDrawPalette( + Com&& paletteProxy, + IUnknown* pParent) + : DDrawWrappedObject(pParent, std::move(paletteProxy), nullptr) { + if (m_parent != nullptr) + m_parent->AddRef(); + + m_paletteCount = ++s_paletteCount; + + Logger::debug(str::format("DDrawPalette: Created a new palette nr. [[1-", m_paletteCount, "]]")); + } + + DDrawPalette::~DDrawPalette() { + if (m_commonSurf != nullptr) + m_commonSurf->SetPalette(nullptr); + + if (m_parent != nullptr) + m_parent->Release(); + + Logger::debug(str::format("DDrawPalette: Palette nr. [[1-", m_paletteCount, "]] bites the dust")); + } + + // Docs state: "Because the DirectDrawPalette object is initialized when + // it is created, this method always returns DDERR_ALREADYINITIALIZED." + HRESULT STDMETHODCALLTYPE DDrawPalette::Initialize(LPDIRECTDRAW lpDD, DWORD dwFlags, LPPALETTEENTRY lpDDColorTable) { + Logger::debug(">>> DDrawPalette::Initialize"); + return DDERR_ALREADYINITIALIZED; + } + + HRESULT STDMETHODCALLTYPE DDrawPalette::GetCaps(LPDWORD lpdwCaps) { + Logger::debug("<<< DDrawPalette::GetCaps: Proxy"); + return m_proxy->GetCaps(lpdwCaps); + } + + HRESULT STDMETHODCALLTYPE DDrawPalette::GetEntries(DWORD dwFlags, DWORD dwBase, DWORD dwNumEntries, LPPALETTEENTRY lpEntries) { + Logger::debug("<<< DDrawPalette::GetEntries: Proxy"); + return m_proxy->GetEntries(dwFlags, dwBase, dwNumEntries, lpEntries); + } + + HRESULT STDMETHODCALLTYPE DDrawPalette::SetEntries(DWORD dwFlags, DWORD dwStartingEntry, DWORD dwCount, LPPALETTEENTRY lpEntries) { + Logger::debug("<<< DDrawPalette::SetEntries: Proxy"); + return m_proxy->SetEntries(dwFlags, dwStartingEntry, dwCount, lpEntries); + } + +} + diff --git a/src/ddraw/ddraw_palette.h b/src/ddraw/ddraw_palette.h new file mode 100644 index 00000000000..86a5118205c --- /dev/null +++ b/src/ddraw/ddraw_palette.h @@ -0,0 +1,41 @@ +#pragma once + +#include "ddraw_include.h" +#include "ddraw_wrapped_object.h" + +namespace dxvk { + + class DDrawCommonSurface; + + class DDrawPalette final : public DDrawWrappedObject { + + public: + + DDrawPalette( + Com&& paletteProxy, + IUnknown* pParent); + + ~DDrawPalette(); + + HRESULT STDMETHODCALLTYPE Initialize(LPDIRECTDRAW lpDD, DWORD dwFlags, LPPALETTEENTRY lpDDColorTable); + + HRESULT STDMETHODCALLTYPE GetCaps(LPDWORD lpdwCaps); + + HRESULT STDMETHODCALLTYPE GetEntries(DWORD dwFlags, DWORD dwBase, DWORD dwNumEntries, LPPALETTEENTRY lpEntries); + + HRESULT STDMETHODCALLTYPE SetEntries(DWORD dwFlags, DWORD dwStartingEntry, DWORD dwCount, LPPALETTEENTRY lpEntries); + + void SetCommonSurface(DDrawCommonSurface* commonSurf) { + m_commonSurf = commonSurf; + } + + private: + + static uint32_t s_paletteCount; + uint32_t m_paletteCount = 0; + + DDrawCommonSurface* m_commonSurf = nullptr; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_util.h b/src/ddraw/ddraw_util.h new file mode 100644 index 00000000000..a792937c576 --- /dev/null +++ b/src/ddraw/ddraw_util.h @@ -0,0 +1,1639 @@ +#pragma once + +#include "ddraw_include.h" +#include "ddraw_options.h" +#include "ddraw_caps.h" + +#include + +namespace dxvk { + + struct PackedVertexBuffer { + std::vector vertexData; + UINT stride; + }; + + struct VertexStreamInfo { + D3DPRIMITIVETYPE d3dpt; + D3DVERTEXTYPE d3dvt; + DWORD dwFlags = 0; + }; + + // MS, in their infinite wisdom, decided to have 3 distinct versions + // of D3DDEVICEDESC, the first shipped with D3D2/3, the second with D3D5, + // and the third (which is what we have in modern headers) with D3D6. + // All 3 sizes are valid in D3D6, and the first 2 are in D3D5, but let's + // consider them all valid and save ourselves some trouble. + inline bool IsValidD3DDeviceDescSize(DWORD size) { + return size == sizeof(D3DDEVICEDESC) + || size == sizeof(D3DDEVICEDESC2) + || size == sizeof(D3DDEVICEDESC3); + } + + inline bool IsVSyncFlipFlag(DWORD flag) { + return !(flag & DDFLIP_NOVSYNC) || + (flag & DDFLIP_INTERVAL2) || + (flag & DDFLIP_INTERVAL3) || + (flag & DDFLIP_INTERVAL4); + } + + // D3D5 D3DVERTEXTYPE to >D3D6 DWORD vertex codes + inline DWORD ConvertVertexType(D3DVERTEXTYPE vertexType) { + switch (vertexType) { + default: + case D3DVT_VERTEX: return D3DFVF_VERTEX; + case D3DVT_LVERTEX: return D3DFVF_LVERTEX; + case D3DVT_TLVERTEX: return D3DFVF_TLVERTEX; + } + } + + // >D3D6 DWORD vertex codes to D3D5 D3DVERTEXTYPE + inline D3DVERTEXTYPE ConvertFVFType(DWORD fvfType) { + switch (fvfType) { + default: + case D3DFVF_VERTEX: return D3DVT_VERTEX; + case D3DFVF_LVERTEX: return D3DVT_LVERTEX; + case D3DFVF_TLVERTEX: return D3DVT_TLVERTEX; + } + } + + inline d3d9::D3DTRANSFORMSTATETYPE ConvertTransformState(D3DTRANSFORMSTATETYPE tst) { + switch (tst) { + case D3DTRANSFORMSTATE_WORLD: return d3d9::D3DTRANSFORMSTATETYPE(D3DTS_WORLD); + case D3DTRANSFORMSTATE_WORLD1: return d3d9::D3DTRANSFORMSTATETYPE(D3DTS_WORLD1); + case D3DTRANSFORMSTATE_WORLD2: return d3d9::D3DTRANSFORMSTATETYPE(D3DTS_WORLD2); + case D3DTRANSFORMSTATE_WORLD3: return d3d9::D3DTRANSFORMSTATETYPE(D3DTS_WORLD3); + default: return d3d9::D3DTRANSFORMSTATETYPE(tst); + } + } + + inline DWORD ConvertD3D6LockFlags(DWORD lockFlags, bool isSurface) { + DWORD lockFlagsD3D9 = 0; + + // DDLOCK_WAIT is default for ddraw7 surfaces, so ignore it + // and only factor in DDLOCK_DONOTWAIT. Buffers locks have + // a flipped logic compared to d3d9. + if ((!isSurface && !(lockFlags & DDLOCK_WAIT)) + || (isSurface && (lockFlags & DDLOCK_DONOTWAIT))) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_DONOTWAIT; + } + if (lockFlags & DDLOCK_NOSYSLOCK) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_NOSYSLOCK; + } + // Not sure if both can happen at the same time, but play it safe + if ((lockFlags & DDLOCK_READONLY) && !(lockFlags & DDLOCK_WRITEONLY)) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_READONLY; + } + if (lockFlags & DDLOCK_NOOVERWRITE) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_NOOVERWRITE; + } + + return lockFlagsD3D9; + } + + inline DWORD ConvertD3D7LockFlags(DWORD lockFlags, bool legacyDiscard, bool isSurface) { + DWORD lockFlagsD3D9 = 0; + + // DDLOCK_WAIT is default for ddraw7 surfaces, so ignore it + // and only factor in DDLOCK_DONOTWAIT. Buffers locks have + // a flipped logic compared to d3d9. + if ((!isSurface && !(lockFlags & DDLOCK_WAIT)) + || (isSurface && (lockFlags & DDLOCK_DONOTWAIT))) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_DONOTWAIT; + } + if (lockFlags & DDLOCK_NOSYSLOCK) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_NOSYSLOCK; + } + // Not sure if both can happen at the same time, but play it safe + if ((lockFlags & DDLOCK_READONLY) && !(lockFlags & DDLOCK_WRITEONLY)) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_READONLY; + } + // This is called "legacy DISCARD" in D8VK, which apparently + // is expected to be enforced implicitly in D3D7. Earlier + // versions of D3D do not feature a DISCARD lock flag. + if ((lockFlags & DDLOCK_DISCARDCONTENTS) && !legacyDiscard) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_DISCARD; + } + if (lockFlags & DDLOCK_NOOVERWRITE) { + lockFlagsD3D9 |= (DWORD)D3DLOCK_NOOVERWRITE; + } + + return lockFlagsD3D9; + } + + inline DWORD ConvertD3D6UsageFlags(DWORD usageFlags, DWORD creationFlags) { + DWORD usageFlagsD3D9 = 0; + + // The D3D6 docs do not mention the presence of a D3DVBCAPS_DONOTCLIP flag, + // and only the creation flag D3DDP_DONOTCLIP is touted as being usable + if ((usageFlags & D3DVBCAPS_DONOTCLIP) || (creationFlags & D3DDP_DONOTCLIP)) { + usageFlagsD3D9 |= (DWORD)D3DUSAGE_DONOTCLIP; + } + if (usageFlags & D3DVBCAPS_WRITEONLY) { + usageFlagsD3D9 |= (DWORD)D3DUSAGE_WRITEONLY; + } + + // D3D6 does not use DDLOCK_DISCARDCONTENTS or + // DDLOCK_NOOVERWRITE, however, still mark all buffers + // as DYNAMIC to handle any potential CPU read-backs + return usageFlagsD3D9 | D3DUSAGE_DYNAMIC; + } + + inline DWORD ConvertD3D7UsageFlags(DWORD usageFlags) { + DWORD usageFlagsD3D9 = 0; + + if (usageFlags & D3DVBCAPS_DONOTCLIP) { + usageFlagsD3D9 |= (DWORD)D3DUSAGE_DONOTCLIP; + } + if (usageFlags & D3DVBCAPS_WRITEONLY) { + usageFlagsD3D9 |= (DWORD)D3DUSAGE_WRITEONLY; + } + + // Though D3D7 does not specify it, all buffers need + // to be DYNAMIC, either due to some lock flags not + // working properly otherwise, or for performance reasons + return usageFlagsD3D9 | D3DUSAGE_DYNAMIC; + } + + inline size_t GetFVFPositionSize(DWORD fvf) { + size_t size = 0; + + switch (fvf & D3DFVF_POSITION_MASK) { + case D3DFVF_XYZ: + size += 3 * sizeof(FLOAT); + break; + case D3DFVF_XYZRHW: + size += 4 * sizeof(FLOAT); + break; + case D3DFVF_XYZB1: + size += 4 * sizeof(FLOAT); + break; + case D3DFVF_XYZB2: + size += 5 * sizeof(FLOAT); + break; + case D3DFVF_XYZB3: + size += 6 * sizeof(FLOAT); + break; + case D3DFVF_XYZB4: + size += 7 * sizeof(FLOAT); + break; + case D3DFVF_XYZB5: + size += 8 * sizeof(FLOAT); + break; + } + + return size; + } + + inline size_t GetFVFTexCoordSize(DWORD fvf, DWORD coord) { + size_t size = 0; + + DWORD texCoordSize = (fvf >> (coord * 2 + 16)) & 0x3; + switch (texCoordSize) { + // D3DFVF_TEXTUREFORMAT2 0 + case D3DFVF_TEXTUREFORMAT2: + size += 2 * sizeof(FLOAT); + break; + // D3DFVF_TEXTUREFORMAT3 1 + case D3DFVF_TEXTUREFORMAT3: + size += 3 * sizeof(FLOAT); + break; + // D3DFVF_TEXTUREFORMAT4 2 + case D3DFVF_TEXTUREFORMAT4: + size += 4 * sizeof(FLOAT); + break; + // D3DFVF_TEXTUREFORMAT1 3 + case D3DFVF_TEXTUREFORMAT1: + size += sizeof(FLOAT); + break; + } + + return size; + } + + inline size_t GetFVFSize(DWORD fvf) { + size_t size = 0; + + size += GetFVFPositionSize(fvf); + + if (fvf & D3DFVF_NORMAL) { + size += 3 * sizeof(FLOAT); + } + if (fvf & D3DFVF_RESERVED1) { + size += sizeof(DWORD); + } + if (fvf & D3DFVF_DIFFUSE) { + size += sizeof(D3DCOLOR); + } + if (fvf & D3DFVF_SPECULAR) { + size += sizeof(D3DCOLOR); + } + + DWORD textureCount = (fvf & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT; + for (DWORD coord = 0; coord < textureCount; ++coord) { + size += GetFVFTexCoordSize(fvf, coord); + } + + return size; + } + + inline UINT GetPrimitiveCount(D3DPRIMITIVETYPE PrimitiveType, DWORD VertexCount) { + switch (PrimitiveType) { + case D3DPT_POINTLIST: return static_cast(VertexCount); + case D3DPT_LINELIST: return static_cast(VertexCount / 2); + case D3DPT_LINESTRIP: return static_cast(VertexCount - 1); + default: + case D3DPT_TRIANGLELIST: return static_cast(VertexCount / 3); + case D3DPT_TRIANGLESTRIP: return static_cast(VertexCount - 2); + case D3DPT_TRIANGLEFAN: return static_cast(VertexCount - 2); + } + } + + inline PackedVertexBuffer TransformStridedtoUP( + DWORD dwFVF, + LPD3DDRAWPRIMITIVESTRIDEDDATA lpVBStrided, + DWORD dwNumVertices) { + PackedVertexBuffer pvb; + pvb.stride = GetFVFSize(dwFVF); + pvb.vertexData.resize(pvb.stride * dwNumVertices); + + DWORD dwNumTextures = (dwFVF & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT; + + for (DWORD i = 0; i < dwNumVertices; i++) { + uint8_t* ptr = pvb.vertexData.data() + i * pvb.stride; + + if ((dwFVF & D3DFVF_POSITION_MASK) && lpVBStrided->position.lpvData) { + size_t size = GetFVFPositionSize(dwFVF); + memcpy(ptr, static_cast(lpVBStrided->position.lpvData) + i * lpVBStrided->position.dwStride, size); + ptr += size; + } + + if ((dwFVF & D3DFVF_NORMAL) && lpVBStrided->normal.lpvData) { + size_t size = 3 * sizeof(FLOAT); + memcpy(ptr, static_cast(lpVBStrided->normal.lpvData) + i * lpVBStrided->normal.dwStride, size); + ptr += size; + } + + if (dwFVF & D3DFVF_RESERVED1) { + // D3DLVERTEX + ptr += sizeof(DWORD); + } + + if ((dwFVF & D3DFVF_DIFFUSE) && lpVBStrided->diffuse.lpvData) { + size_t size = sizeof(D3DCOLOR); + memcpy(ptr, static_cast(lpVBStrided->diffuse.lpvData) + i * lpVBStrided->diffuse.dwStride, size); + ptr += size; + } + + if ((dwFVF & D3DFVF_SPECULAR) && lpVBStrided->specular.lpvData) { + size_t size = sizeof(D3DCOLOR); + memcpy(ptr, static_cast(lpVBStrided->specular.lpvData) + i * lpVBStrided->specular.dwStride, size); + ptr += size; + } + + for (DWORD t = 0; t < dwNumTextures; t++) { + if (lpVBStrided->textureCoords[t].lpvData) { + size_t size = GetFVFTexCoordSize(dwFVF, t); + memcpy(ptr, static_cast(lpVBStrided->textureCoords[t].lpvData) + i * lpVBStrided->textureCoords[t].dwStride, size); + ptr += size; + } + } + } + + return pvb; + } + + // If this D3DTEXTURESTAGESTATETYPE has been remapped to a d3d9::D3DSAMPLERSTATETYPE + // it will be returned, otherwise returns -1u + inline d3d9::D3DSAMPLERSTATETYPE ConvertSamplerStateType(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_MAXMIPLEVEL; + case D3DTSS_MAXANISOTROPY: return d3d9::D3DSAMP_MAXANISOTROPY; + default: return d3d9::D3DSAMPLERSTATETYPE(-1u); + } + } + + inline DWORD DecodeD3D7TexFilterValues(const D3DTEXTURESTAGESTATETYPE StageType, const DWORD FilterType7) { + switch (StageType) { + case D3DTSS_MAGFILTER: { + switch (FilterType7) { + default: + case D3DTFG_POINT: return d3d9::D3DTEXF_POINT; + case D3DTFG_LINEAR: return d3d9::D3DTEXF_LINEAR; + // 5 in D3DTEXTUREMAGFILTER, 3 in D3DTEXTUREFILTERTYPE + case D3DTFG_ANISOTROPIC: return d3d9::D3DTEXF_ANISOTROPIC; + } + break; + } + case D3DTSS_MINFILTER: { + switch (FilterType7) { + default: + case D3DTFN_POINT: return d3d9::D3DTEXF_POINT; + case D3DTFN_LINEAR: return d3d9::D3DTEXF_LINEAR; + case D3DTFN_ANISOTROPIC: return d3d9::D3DTEXF_ANISOTROPIC; + } + break; + } + case D3DTSS_MIPFILTER: { + switch (FilterType7) { + // All values in D3DTEXTUREMIPFILTER are offset by +1 + // vs D3DTEXTUREFILTERTYPE... + default: + case D3DTFP_NONE: return d3d9::D3DTEXF_NONE; + case D3DTFP_POINT: return d3d9::D3DTEXF_POINT; + case D3DTFP_LINEAR: return d3d9::D3DTEXF_LINEAR; + } + break; + } + default: return 0; + } + } + + inline DWORD DecodeD3D9TexFilterValues(const D3DTEXTURESTAGESTATETYPE StageType, const DWORD FilterType9) { + switch (StageType) { + case D3DTSS_MAGFILTER: { + switch (FilterType9) { + default: + case d3d9::D3DTEXF_POINT: return D3DTFG_POINT; + case d3d9::D3DTEXF_LINEAR: return D3DTFG_LINEAR; + // 5 in D3DTEXTUREMAGFILTER, 3 in D3DTEXTUREFILTERTYPE + case d3d9::D3DTEXF_ANISOTROPIC: return D3DTFG_ANISOTROPIC; + } + break; + } + case D3DTSS_MINFILTER: { + switch (FilterType9) { + default: + case d3d9::D3DTEXF_POINT: return D3DTFN_POINT; + case d3d9::D3DTEXF_LINEAR: return D3DTFN_LINEAR; + case d3d9::D3DTEXF_ANISOTROPIC: return D3DTFN_ANISOTROPIC; + } + break; + } + case D3DTSS_MIPFILTER: { + switch (FilterType9) { + // All values in D3DTEXTUREMIPFILTER are offset by +1 + // vs D3DTEXTUREFILTERTYPE... + default: + case d3d9::D3DTEXF_NONE: return D3DTFP_NONE; + case d3d9::D3DTEXF_POINT: return D3DTFP_POINT; + case d3d9::D3DTEXF_LINEAR: return D3DTFP_LINEAR; + } + break; + } + default: return 0; + } + } + + inline DWORD DecodeTextureMinValues(DWORD minFilter, DWORD mipFilter) { + if (minFilter == d3d9::D3DTEXF_POINT) { + switch(mipFilter) { + default: + case d3d9::D3DTEXF_NONE: return D3DFILTER_NEAREST; + case d3d9::D3DTEXF_POINT: return D3DFILTER_MIPNEAREST; + case d3d9::D3DTEXF_LINEAR: return D3DFILTER_LINEARMIPNEAREST; + } + } else if (minFilter == d3d9::D3DTEXF_LINEAR) { + switch(mipFilter) { + default: + case d3d9::D3DTEXF_NONE: return D3DFILTER_LINEAR; + case d3d9::D3DTEXF_POINT: return D3DFILTER_MIPLINEAR; + case d3d9::D3DTEXF_LINEAR: return D3DFILTER_LINEARMIPLINEAR; + } + } + return 0; + } + + inline D3DDEVICEDESC3 GetD3D3Caps(const D3DOptions* options) { + D3DDEVICEDESC3 desc; + + desc.dwSize = sizeof(D3DDEVICEDESC3); + + desc.dwFlags = D3DDD_BCLIPPING + | D3DDD_COLORMODEL + | D3DDD_DEVCAPS + | D3DDD_DEVICERENDERBITDEPTH + | D3DDD_DEVICEZBUFFERBITDEPTH + | D3DDD_LIGHTINGCAPS + | D3DDD_LINECAPS + | D3DDD_MAXBUFFERSIZE + | D3DDD_MAXVERTEXCOUNT + | D3DDD_TRANSFORMCAPS + | D3DDD_TRICAPS; + + desc.dcmColorModel = D3DCOLOR_RGB; + + desc.dwDevCaps = D3DDEVCAPS_EXECUTESYSTEMMEMORY + | D3DDEVCAPS_EXECUTEVIDEOMEMORY + | D3DDEVCAPS_FLOATTLVERTEX + // | D3DDEVCAPS_SORTDECREASINGZ + // | D3DDEVCAPS_SORTEXACT + // | D3DDEVCAPS_SORTINCREASINGZ + // | D3DDEVCAPS_TEXTURESYSTEMMEMORY + | D3DDEVCAPS_TEXTUREVIDEOMEMORY + | D3DDEVCAPS_TLVERTEXSYSTEMMEMORY + | D3DDEVCAPS_TLVERTEXVIDEOMEMORY; + + D3DTRANSFORMCAPS transformCaps; + transformCaps.dwSize = sizeof(D3DTRANSFORMCAPS); + transformCaps.dwCaps = D3DTRANSFORMCAPS_CLIP; + + desc.dtcTransformCaps = transformCaps; + + desc.bClipping = TRUE; + + D3DLIGHTINGCAPS lightingCaps; + lightingCaps.dwSize = sizeof(D3DLIGHTINGCAPS); + lightingCaps.dwCaps = D3DLIGHTCAPS_DIRECTIONAL + // | D3DLIGHTCAPS_GLSPOT // D3D3 specific + // | D3DLIGHTCAPS_PARALLELPOINT // Not supported by D3D9 + | D3DLIGHTCAPS_POINT + | D3DLIGHTCAPS_SPOT; + lightingCaps.dwLightingModel = D3DLIGHTINGMODEL_RGB; + lightingCaps.dwNumLights = ddrawCaps::MaxEnabledLights; + + desc.dlcLightingCaps = lightingCaps; + + D3DPRIMCAPS prim; + prim.dwSize = sizeof(D3DPRIMCAPS); + + prim.dwMiscCaps = D3DPMISCCAPS_CULLCCW + | D3DPMISCCAPS_CULLCW + | D3DPMISCCAPS_CULLNONE + // | D3DPMISCCAPS_CONFORMANT + // | D3DPMISCCAPS_LINEPATTERNREP // Not implemented in D3D9 + // | D3DPMISCCAPS_MASKPLANES + | D3DPMISCCAPS_MASKZ; + + prim.dwRasterCaps = D3DPRASTERCAPS_DITHER + | D3DPRASTERCAPS_FOGTABLE + | D3DPRASTERCAPS_FOGVERTEX + // | D3DPRASTERCAPS_PAT // Not implemented in D3D9 + // | D3DPRASTERCAPS_ROP2 + | D3DPRASTERCAPS_STIPPLE // Technically not implemented + | D3DPRASTERCAPS_SUBPIXEL + // | D3DPRASTERCAPS_SUBPIXELX + // | D3DPRASTERCAPS_XOR + | D3DPRASTERCAPS_ZTEST; + + prim.dwZCmpCaps = D3DPCMPCAPS_ALWAYS + | D3DPCMPCAPS_EQUAL + | D3DPCMPCAPS_GREATER + | D3DPCMPCAPS_GREATEREQUAL + | D3DPCMPCAPS_LESS + | D3DPCMPCAPS_LESSEQUAL + | D3DPCMPCAPS_NEVER + | D3DPCMPCAPS_NOTEQUAL; + + prim.dwSrcBlendCaps = D3DPBLENDCAPS_BOTHINVSRCALPHA + | D3DPBLENDCAPS_BOTHSRCALPHA + | D3DPBLENDCAPS_DESTALPHA + | D3DPBLENDCAPS_DESTCOLOR + | D3DPBLENDCAPS_INVDESTALPHA + | D3DPBLENDCAPS_INVDESTCOLOR + | D3DPBLENDCAPS_INVSRCALPHA + | D3DPBLENDCAPS_INVSRCCOLOR + | D3DPBLENDCAPS_ONE + | D3DPBLENDCAPS_SRCALPHA + | D3DPBLENDCAPS_SRCALPHASAT + | D3DPBLENDCAPS_SRCCOLOR + | D3DPBLENDCAPS_ZERO; + + prim.dwDestBlendCaps = prim.dwSrcBlendCaps; + + prim.dwAlphaCmpCaps = prim.dwZCmpCaps; + + prim.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND + // | D3DPSHADECAPS_ALPHAFLATSTIPPLED + | D3DPSHADECAPS_ALPHAGOURAUDBLEND + // | D3DPSHADECAPS_ALPHAGOURAUDSTIPPLED + // | D3DPSHADECAPS_ALPHAPHONGBLEND + // | D3DPSHADECAPS_ALPHAPHONGSTIPPLED + // | D3DPSHADECAPS_COLORFLATMONO + | D3DPSHADECAPS_COLORFLATRGB + // | D3DPSHADECAPS_COLORGOURAUDMONO + | D3DPSHADECAPS_COLORGOURAUDRGB + // | D3DPSHADECAPS_COLORPHONGMONO + // | D3DPSHADECAPS_COLORPHONGRGB + | D3DPSHADECAPS_FOGFLAT + | D3DPSHADECAPS_FOGGOURAUD + // | D3DPSHADECAPS_FOGPHONG + // | D3DPSHADECAPS_SPECULARFLATMONO + | D3DPSHADECAPS_SPECULARFLATRGB + // | D3DPSHADECAPS_SPECULARGOURAUDMONO + | D3DPSHADECAPS_SPECULARGOURAUDRGB; + // | D3DPSHADECAPS_SPECULARPHONGMONO + // | D3DPSHADECAPS_SPECULARPHONGRGB; + + prim.dwTextureCaps = D3DPTEXTURECAPS_ALPHA + | D3DPTEXTURECAPS_BORDER + | D3DPTEXTURECAPS_PERSPECTIVE + // | D3DPTEXTURECAPS_SQUAREONLY + | D3DPTEXTURECAPS_TRANSPARENCY; + + if (unlikely(options->forcePOW2Textures)) { + prim.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + } + + prim.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR + | D3DPTFILTERCAPS_LINEARMIPLINEAR + | D3DPTFILTERCAPS_LINEARMIPNEAREST + | D3DPTFILTERCAPS_MIPLINEAR + | D3DPTFILTERCAPS_MIPNEAREST + | D3DPTFILTERCAPS_NEAREST; + + prim.dwTextureBlendCaps = D3DPTBLENDCAPS_COPY + | D3DPTBLENDCAPS_DECAL + | D3DPTBLENDCAPS_DECALALPHA + | D3DPTBLENDCAPS_DECALMASK + | D3DPTBLENDCAPS_MODULATE + | D3DPTBLENDCAPS_MODULATEALPHA + | D3DPTBLENDCAPS_MODULATEMASK; + + prim.dwTextureAddressCaps = D3DPTADDRESSCAPS_CLAMP + | D3DPTADDRESSCAPS_MIRROR + | D3DPTADDRESSCAPS_WRAP; + + prim.dwStippleWidth = 32; + prim.dwStippleHeight = 32; + + desc.dpcLineCaps = prim; + desc.dpcTriCaps = prim; + + desc.dwDeviceRenderBitDepth = DDBD_16 | DDBD_24 | DDBD_32; + desc.dwDeviceZBufferBitDepth = options->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + desc.dwMaxBufferSize = 0; + desc.dwMaxVertexCount = D3DMAXNUMVERTICES; + + return desc; + } + + inline D3DDEVICEDESC2 GetD3D5Caps(const IID rclsid, const D3DOptions* options) { + D3DDEVICEDESC2 desc; + + desc.dwSize = sizeof(D3DDEVICEDESC2); + + desc.dwFlags = D3DDD_BCLIPPING + | D3DDD_COLORMODEL + | D3DDD_DEVCAPS + | D3DDD_DEVICERENDERBITDEPTH + | D3DDD_DEVICEZBUFFERBITDEPTH + | D3DDD_LIGHTINGCAPS + | D3DDD_LINECAPS + | D3DDD_MAXBUFFERSIZE + | D3DDD_MAXVERTEXCOUNT + | D3DDD_TRANSFORMCAPS + | D3DDD_TRICAPS; + + desc.dcmColorModel = D3DCOLOR_RGB; + + desc.dwDevCaps = D3DDEVCAPS_CANRENDERAFTERFLIP + | D3DDEVCAPS_DRAWPRIMTLVERTEX + | D3DDEVCAPS_EXECUTESYSTEMMEMORY + | D3DDEVCAPS_EXECUTEVIDEOMEMORY + | D3DDEVCAPS_FLOATTLVERTEX + // | D3DDEVCAPS_HWRASTERIZATION + // | D3DDEVCAPS_HWTRANSFORMANDLIGHT + // | D3DDEVCAPS_DRAWPRIMITIVES2 + // | D3DDEVCAPS_SEPARATETEXTUREMEMORIES + // | D3DDEVCAPS_DRAWPRIMITIVES2EX + // | D3DDEVCAPS_SORTDECREASINGZ + // | D3DDEVCAPS_SORTEXACT + // | D3DDEVCAPS_SORTINCREASINGZ + | D3DDEVCAPS_TEXTURENONLOCALVIDMEM + // | D3DDEVCAPS_TEXTURESYSTEMMEMORY + | D3DDEVCAPS_TEXTUREVIDEOMEMORY + | D3DDEVCAPS_TLVERTEXSYSTEMMEMORY + | D3DDEVCAPS_TLVERTEXVIDEOMEMORY; + + if (rclsid == IID_IDirect3DHALDevice) { + desc.dwDevCaps |= D3DDEVCAPS_HWRASTERIZATION + | D3DDEVCAPS_HWTRANSFORMANDLIGHT // Also advertised in D3D5 + | D3DDEVCAPS_DRAWPRIMITIVES2 + | D3DDEVCAPS_DRAWPRIMITIVES2EX; + } + + D3DTRANSFORMCAPS transformCaps; + transformCaps.dwSize = sizeof(D3DTRANSFORMCAPS); + transformCaps.dwCaps = D3DTRANSFORMCAPS_CLIP; + + desc.dtcTransformCaps = transformCaps; + + desc.bClipping = TRUE; + + D3DLIGHTINGCAPS lightingCaps; + lightingCaps.dwSize = sizeof(D3DLIGHTINGCAPS); + lightingCaps.dwCaps = D3DLIGHTCAPS_DIRECTIONAL + // | D3DLIGHTCAPS_PARALLELPOINT // Not supported by D3D9 + | D3DLIGHTCAPS_POINT + | D3DLIGHTCAPS_SPOT; + lightingCaps.dwLightingModel = D3DLIGHTINGMODEL_RGB; + lightingCaps.dwNumLights = ddrawCaps::MaxEnabledLights; + + desc.dlcLightingCaps = lightingCaps; + + D3DPRIMCAPS prim; + prim.dwSize = sizeof(D3DPRIMCAPS); + + prim.dwMiscCaps = D3DPMISCCAPS_CULLCCW + | D3DPMISCCAPS_CULLCW + | D3DPMISCCAPS_CULLNONE + // | D3DPMISCCAPS_CONFORMANT + // | D3DPMISCCAPS_LINEPATTERNREP // Not implemented in D3D9 + // | D3DPMISCCAPS_MASKPLANES + | D3DPMISCCAPS_MASKZ; + + prim.dwRasterCaps = D3DPRASTERCAPS_ANISOTROPY + | D3DPRASTERCAPS_ANTIALIASEDGES // Technically not implemented in D3D9 + // | D3DPRASTERCAPS_ANTIALIASSORTDEPENDENT + // | D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT + | D3DPRASTERCAPS_DITHER + | D3DPRASTERCAPS_FOGRANGE + | D3DPRASTERCAPS_FOGTABLE + | D3DPRASTERCAPS_FOGVERTEX + | D3DPRASTERCAPS_MIPMAPLODBIAS + // | D3DPRASTERCAPS_PAT // Not implemented in D3D9 + // | D3DPRASTERCAPS_ROP2 + | D3DPRASTERCAPS_STIPPLE // Technically not implemented + | D3DPRASTERCAPS_SUBPIXEL + // | D3DPRASTERCAPS_SUBPIXELX + // | D3DPRASTERCAPS_TRANSLUCENTSORTINDEPENDENT + // | D3DPRASTERCAPS_WBUFFER + | D3DPRASTERCAPS_WFOG + // | D3DPRASTERCAPS_XOR + | D3DPRASTERCAPS_ZBIAS + // | D3DPRASTERCAPS_ZBUFFERLESSHSR // Easy footgun to not get a z-buffer + | D3DPRASTERCAPS_ZFOG + | D3DPRASTERCAPS_ZTEST; + + if (unlikely(options->emulateFSAA != FSAAEmulation::Disabled)) { + prim.dwRasterCaps |= D3DPRASTERCAPS_ANTIALIASSORTDEPENDENT + | D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT; + } + + prim.dwZCmpCaps = D3DPCMPCAPS_ALWAYS + | D3DPCMPCAPS_EQUAL + | D3DPCMPCAPS_GREATER + | D3DPCMPCAPS_GREATEREQUAL + | D3DPCMPCAPS_LESS + | D3DPCMPCAPS_LESSEQUAL + | D3DPCMPCAPS_NEVER + | D3DPCMPCAPS_NOTEQUAL; + + prim.dwSrcBlendCaps = D3DPBLENDCAPS_BOTHINVSRCALPHA + | D3DPBLENDCAPS_BOTHSRCALPHA + | D3DPBLENDCAPS_DESTALPHA + | D3DPBLENDCAPS_DESTCOLOR + | D3DPBLENDCAPS_INVDESTALPHA + | D3DPBLENDCAPS_INVDESTCOLOR + | D3DPBLENDCAPS_INVSRCALPHA + | D3DPBLENDCAPS_INVSRCCOLOR + | D3DPBLENDCAPS_ONE + | D3DPBLENDCAPS_SRCALPHA + | D3DPBLENDCAPS_SRCALPHASAT + | D3DPBLENDCAPS_SRCCOLOR + | D3DPBLENDCAPS_ZERO; + + prim.dwDestBlendCaps = prim.dwSrcBlendCaps; + + prim.dwAlphaCmpCaps = prim.dwZCmpCaps; + + prim.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND + // | D3DPSHADECAPS_ALPHAFLATSTIPPLED + | D3DPSHADECAPS_ALPHAGOURAUDBLEND + // | D3DPSHADECAPS_ALPHAGOURAUDSTIPPLED + // | D3DPSHADECAPS_ALPHAPHONGBLEND + // | D3DPSHADECAPS_ALPHAPHONGSTIPPLED + // | D3DPSHADECAPS_COLORFLATMONO + | D3DPSHADECAPS_COLORFLATRGB + // | D3DPSHADECAPS_COLORGOURAUDMONO + | D3DPSHADECAPS_COLORGOURAUDRGB + // | D3DPSHADECAPS_COLORPHONGMONO + // | D3DPSHADECAPS_COLORPHONGRGB + | D3DPSHADECAPS_FOGFLAT + | D3DPSHADECAPS_FOGGOURAUD + // | D3DPSHADECAPS_FOGPHONG + // | D3DPSHADECAPS_SPECULARFLATMONO + | D3DPSHADECAPS_SPECULARFLATRGB + // | D3DPSHADECAPS_SPECULARGOURAUDMONO + | D3DPSHADECAPS_SPECULARGOURAUDRGB; + // | D3DPSHADECAPS_SPECULARPHONGMONO + // | D3DPSHADECAPS_SPECULARPHONGRGB; + + prim.dwTextureCaps = D3DPTEXTURECAPS_ALPHA + | D3DPTEXTURECAPS_BORDER + | D3DPTEXTURECAPS_PERSPECTIVE + // | D3DPTEXTURECAPS_SQUAREONLY + | D3DPTEXTURECAPS_TRANSPARENCY; + + if (unlikely(options->forcePOW2Textures)) { + prim.dwTextureCaps |= D3DPTEXTURECAPS_POW2; + } + + prim.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR + | D3DPTFILTERCAPS_LINEARMIPLINEAR + | D3DPTFILTERCAPS_LINEARMIPNEAREST + | D3DPTFILTERCAPS_MIPLINEAR + | D3DPTFILTERCAPS_MIPNEAREST + | D3DPTFILTERCAPS_NEAREST; + + prim.dwTextureBlendCaps = D3DPTBLENDCAPS_ADD + | D3DPTBLENDCAPS_COPY + | D3DPTBLENDCAPS_DECAL + | D3DPTBLENDCAPS_DECALALPHA + | D3DPTBLENDCAPS_DECALMASK + | D3DPTBLENDCAPS_MODULATE + | D3DPTBLENDCAPS_MODULATEALPHA + | D3DPTBLENDCAPS_MODULATEMASK; + + prim.dwTextureAddressCaps = D3DPTADDRESSCAPS_BORDER + | D3DPTADDRESSCAPS_CLAMP + | D3DPTADDRESSCAPS_INDEPENDENTUV + | D3DPTADDRESSCAPS_MIRROR + | D3DPTADDRESSCAPS_WRAP; + + prim.dwStippleWidth = 32; + prim.dwStippleHeight = 32; + + desc.dpcLineCaps = prim; + desc.dpcTriCaps = prim; + + desc.dwDeviceRenderBitDepth = DDBD_16 | DDBD_24 | DDBD_32; + desc.dwDeviceZBufferBitDepth = options->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + desc.dwMaxBufferSize = 0; + desc.dwMaxVertexCount = D3DMAXNUMVERTICES; + desc.dwMinTextureWidth = 1; + desc.dwMinTextureHeight = 1; + desc.dwMaxTextureWidth = ddrawCaps::MaxTextureDimension; + desc.dwMaxTextureHeight = ddrawCaps::MaxTextureDimension; + desc.dwMinStippleWidth = 1; + desc.dwMinStippleHeight = 1; + desc.dwMaxStippleWidth = 32; + desc.dwMaxStippleHeight = 32; + + return desc; + } + + inline D3DDEVICEDESC GetD3D6Caps(const IID rclsid, const D3DOptions* options) { + D3DDEVICEDESC desc; + + desc.dwSize = sizeof(D3DDEVICEDESC); + + desc.dwFlags = D3DDD_BCLIPPING + | D3DDD_COLORMODEL + | D3DDD_DEVCAPS + | D3DDD_DEVICERENDERBITDEPTH + | D3DDD_DEVICEZBUFFERBITDEPTH + | D3DDD_LIGHTINGCAPS + | D3DDD_LINECAPS + | D3DDD_MAXBUFFERSIZE + | D3DDD_MAXVERTEXCOUNT + | D3DDD_TRANSFORMCAPS + | D3DDD_TRICAPS; + + desc.dcmColorModel = D3DCOLOR_RGB; + + desc.dwDevCaps = D3DDEVCAPS_CANRENDERAFTERFLIP + | D3DDEVCAPS_DRAWPRIMTLVERTEX + | D3DDEVCAPS_EXECUTESYSTEMMEMORY + | D3DDEVCAPS_EXECUTEVIDEOMEMORY + | D3DDEVCAPS_FLOATTLVERTEX + // | D3DDEVCAPS_HWRASTERIZATION + // | D3DDEVCAPS_HWTRANSFORMANDLIGHT + // | D3DDEVCAPS_DRAWPRIMITIVES2 + // | D3DDEVCAPS_SEPARATETEXTUREMEMORIES + // | D3DDEVCAPS_DRAWPRIMITIVES2EX + // | D3DDEVCAPS_SORTDECREASINGZ + // | D3DDEVCAPS_SORTEXACT + // | D3DDEVCAPS_SORTINCREASINGZ + | D3DDEVCAPS_TEXTURENONLOCALVIDMEM + // | D3DDEVCAPS_TEXTURESYSTEMMEMORY + | D3DDEVCAPS_TEXTUREVIDEOMEMORY + | D3DDEVCAPS_TLVERTEXSYSTEMMEMORY + | D3DDEVCAPS_TLVERTEXVIDEOMEMORY; + + if (rclsid == IID_IDirect3DHALDevice) { + desc.dwDevCaps |= D3DDEVCAPS_HWRASTERIZATION + | D3DDEVCAPS_HWTRANSFORMANDLIGHT // Also advertised in D3D6 + | D3DDEVCAPS_DRAWPRIMITIVES2 + | D3DDEVCAPS_DRAWPRIMITIVES2EX; + } + + D3DTRANSFORMCAPS transformCaps; + transformCaps.dwSize = sizeof(D3DTRANSFORMCAPS); + transformCaps.dwCaps = D3DTRANSFORMCAPS_CLIP; + + desc.dtcTransformCaps = transformCaps; + + desc.bClipping = TRUE; + + D3DLIGHTINGCAPS lightingCaps; + lightingCaps.dwSize = sizeof(D3DLIGHTINGCAPS); + lightingCaps.dwCaps = D3DLIGHTCAPS_DIRECTIONAL + // | D3DLIGHTCAPS_GLSPOT + // | D3DLIGHTCAPS_PARALLELPOINT // Not supported by D3D9 + | D3DLIGHTCAPS_POINT + | D3DLIGHTCAPS_SPOT; + lightingCaps.dwLightingModel = D3DLIGHTINGMODEL_RGB; + lightingCaps.dwNumLights = ddrawCaps::MaxEnabledLights; + + desc.dlcLightingCaps = lightingCaps; + + D3DPRIMCAPS prim; + prim.dwSize = sizeof(D3DPRIMCAPS); + + prim.dwMiscCaps = D3DPMISCCAPS_CULLCCW + | D3DPMISCCAPS_CULLCW + | D3DPMISCCAPS_CULLNONE + // | D3DPMISCCAPS_CONFORMANT + // | D3DPMISCCAPS_LINEPATTERNREP // Not implemented in D3D9 + // | D3DPMISCCAPS_MASKPLANES + | D3DPMISCCAPS_MASKZ; + + prim.dwRasterCaps = D3DPRASTERCAPS_ANISOTROPY + | D3DPRASTERCAPS_ANTIALIASEDGES // Technically not implemented in D3D9 + // | D3DPRASTERCAPS_ANTIALIASSORTDEPENDENT + // | D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT + | D3DPRASTERCAPS_DITHER + | D3DPRASTERCAPS_FOGRANGE + | D3DPRASTERCAPS_FOGTABLE + | D3DPRASTERCAPS_FOGVERTEX + | D3DPRASTERCAPS_MIPMAPLODBIAS + // | D3DPRASTERCAPS_PAT // Not implemented in D3D9 + // | D3DPRASTERCAPS_ROP2 + | D3DPRASTERCAPS_STIPPLE // Technically not implemented + | D3DPRASTERCAPS_SUBPIXEL + // | D3DPRASTERCAPS_SUBPIXELX + // | D3DPRASTERCAPS_TRANSLUCENTSORTINDEPENDENT + // | D3DPRASTERCAPS_WBUFFER + | D3DPRASTERCAPS_WFOG + // | D3DPRASTERCAPS_XOR + | D3DPRASTERCAPS_ZBIAS + // | D3DPRASTERCAPS_ZBUFFERLESSHSR // Easy footgun to not get a z-buffer + | D3DPRASTERCAPS_ZFOG + | D3DPRASTERCAPS_ZTEST; + + if (unlikely(options->emulateFSAA != FSAAEmulation::Disabled)) { + prim.dwRasterCaps |= D3DPRASTERCAPS_ANTIALIASSORTDEPENDENT + | D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT; + } + + prim.dwZCmpCaps = D3DPCMPCAPS_ALWAYS + | D3DPCMPCAPS_EQUAL + | D3DPCMPCAPS_GREATER + | D3DPCMPCAPS_GREATEREQUAL + | D3DPCMPCAPS_LESS + | D3DPCMPCAPS_LESSEQUAL + | D3DPCMPCAPS_NEVER + | D3DPCMPCAPS_NOTEQUAL; + + prim.dwSrcBlendCaps = D3DPBLENDCAPS_BOTHINVSRCALPHA + | D3DPBLENDCAPS_BOTHSRCALPHA + | D3DPBLENDCAPS_DESTALPHA + | D3DPBLENDCAPS_DESTCOLOR + | D3DPBLENDCAPS_INVDESTALPHA + | D3DPBLENDCAPS_INVDESTCOLOR + | D3DPBLENDCAPS_INVSRCALPHA + | D3DPBLENDCAPS_INVSRCCOLOR + | D3DPBLENDCAPS_ONE + | D3DPBLENDCAPS_SRCALPHA + | D3DPBLENDCAPS_SRCALPHASAT + | D3DPBLENDCAPS_SRCCOLOR + | D3DPBLENDCAPS_ZERO; + + prim.dwDestBlendCaps = prim.dwSrcBlendCaps; + + prim.dwAlphaCmpCaps = prim.dwZCmpCaps; + + prim.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND + // | D3DPSHADECAPS_ALPHAFLATSTIPPLED + | D3DPSHADECAPS_ALPHAGOURAUDBLEND + // | D3DPSHADECAPS_ALPHAGOURAUDSTIPPLED + // | D3DPSHADECAPS_ALPHAPHONGBLEND + // | D3DPSHADECAPS_ALPHAPHONGSTIPPLED + // | D3DPSHADECAPS_COLORFLATMONO + | D3DPSHADECAPS_COLORFLATRGB + // | D3DPSHADECAPS_COLORGOURAUDMONO + | D3DPSHADECAPS_COLORGOURAUDRGB + // | D3DPSHADECAPS_COLORPHONGMONO + // | D3DPSHADECAPS_COLORPHONGRGB + | D3DPSHADECAPS_FOGFLAT + | D3DPSHADECAPS_FOGGOURAUD + // | D3DPSHADECAPS_FOGPHONG + // | D3DPSHADECAPS_SPECULARFLATMONO + | D3DPSHADECAPS_SPECULARFLATRGB + // | D3DPSHADECAPS_SPECULARGOURAUDMONO + | D3DPSHADECAPS_SPECULARGOURAUDRGB; + // | D3DPSHADECAPS_SPECULARPHONGMONO + // | D3DPSHADECAPS_SPECULARPHONGRGB; + + prim.dwTextureCaps = D3DPTEXTURECAPS_ALPHA + | D3DPTEXTURECAPS_ALPHAPALETTE + | D3DPTEXTURECAPS_BORDER + | D3DPTEXTURECAPS_PERSPECTIVE + // | D3DPTEXTURECAPS_SQUAREONLY + | D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE + | D3DPTEXTURECAPS_TRANSPARENCY; + + if (unlikely(options->forcePOW2Textures)) { + prim.dwTextureCaps |= D3DPTEXTURECAPS_NONPOW2CONDITIONAL + | D3DPTEXTURECAPS_POW2; + } + + prim.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR + | D3DPTFILTERCAPS_LINEARMIPLINEAR + | D3DPTFILTERCAPS_LINEARMIPNEAREST + | D3DPTFILTERCAPS_MIPLINEAR + | D3DPTFILTERCAPS_MIPNEAREST + | D3DPTFILTERCAPS_NEAREST + // | D3DPTFILTERCAPS_MAGFAFLATCUBIC + | D3DPTFILTERCAPS_MAGFANISOTROPIC + // | D3DPTFILTERCAPS_MAGFGAUSSIANCUBIC + | D3DPTFILTERCAPS_MAGFLINEAR + | D3DPTFILTERCAPS_MAGFPOINT + | D3DPTFILTERCAPS_MINFANISOTROPIC + | D3DPTFILTERCAPS_MINFLINEAR + | D3DPTFILTERCAPS_MINFPOINT + | D3DPTFILTERCAPS_MIPFLINEAR + | D3DPTFILTERCAPS_MIPFPOINT; + + prim.dwTextureBlendCaps = D3DPTBLENDCAPS_ADD + | D3DPTBLENDCAPS_COPY + | D3DPTBLENDCAPS_DECAL + | D3DPTBLENDCAPS_DECALALPHA + | D3DPTBLENDCAPS_DECALMASK + | D3DPTBLENDCAPS_MODULATE + | D3DPTBLENDCAPS_MODULATEALPHA + | D3DPTBLENDCAPS_MODULATEMASK; + + prim.dwTextureAddressCaps = D3DPTADDRESSCAPS_BORDER + | D3DPTADDRESSCAPS_CLAMP + | D3DPTADDRESSCAPS_INDEPENDENTUV + | D3DPTADDRESSCAPS_MIRROR + | D3DPTADDRESSCAPS_WRAP; + + prim.dwStippleWidth = 32; + prim.dwStippleHeight = 32; + + desc.dpcLineCaps = prim; + desc.dpcTriCaps = prim; + + desc.dwDeviceRenderBitDepth = DDBD_16 | DDBD_24 | DDBD_32; + desc.dwDeviceZBufferBitDepth = options->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + desc.dwMaxBufferSize = 0; + desc.dwMaxVertexCount = D3DMAXNUMVERTICES; + desc.dwMinTextureWidth = 1; + desc.dwMinTextureHeight = 1; + desc.dwMaxTextureWidth = ddrawCaps::MaxTextureDimension; + desc.dwMaxTextureHeight = ddrawCaps::MaxTextureDimension; + desc.dwMinStippleWidth = 1; + desc.dwMinStippleHeight = 1; + desc.dwMaxStippleWidth = 32; + desc.dwMaxStippleHeight = 32; + desc.dwMaxTextureRepeat = 8192; + desc.dwMaxTextureAspectRatio = ddrawCaps::MaxTextureDimension; + desc.dwMaxAnisotropy = 16; + desc.dvGuardBandLeft = -32768.0f; + desc.dvGuardBandTop = -32768.0f; + desc.dvGuardBandRight = 32768.0f; + desc.dvGuardBandBottom = 32768.0f; + desc.dvExtentsAdjust = 0.0f; + + desc.dwStencilCaps = D3DSTENCILCAPS_DECR + | D3DSTENCILCAPS_DECRSAT + | D3DSTENCILCAPS_INCR + | D3DSTENCILCAPS_INCRSAT + | D3DSTENCILCAPS_INVERT + | D3DSTENCILCAPS_KEEP + | D3DSTENCILCAPS_REPLACE + | D3DSTENCILCAPS_ZERO; + + desc.dwFVFCaps = (ddrawCaps::MaxSimultaneousTextures & D3DFVFCAPS_TEXCOORDCOUNTMASK); + // | D3DFVFCAPS_DONOTSTRIPELEMENTS; + + desc.dwTextureOpCaps = D3DTEXOPCAPS_ADD + | D3DTEXOPCAPS_ADDSIGNED + | D3DTEXOPCAPS_ADDSIGNED2X + | D3DTEXOPCAPS_ADDSMOOTH + | D3DTEXOPCAPS_BLENDCURRENTALPHA + | D3DTEXOPCAPS_BLENDDIFFUSEALPHA + | D3DTEXOPCAPS_BLENDFACTORALPHA + | D3DTEXOPCAPS_BLENDTEXTUREALPHA + | D3DTEXOPCAPS_BLENDTEXTUREALPHAPM + | D3DTEXOPCAPS_BUMPENVMAP + | D3DTEXOPCAPS_BUMPENVMAPLUMINANCE + | D3DTEXOPCAPS_DISABLE + | D3DTEXOPCAPS_DOTPRODUCT3 + | D3DTEXOPCAPS_MODULATE + | D3DTEXOPCAPS_MODULATE2X + | D3DTEXOPCAPS_MODULATE4X + | D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR + | D3DTEXOPCAPS_MODULATECOLOR_ADDALPHA + | D3DTEXOPCAPS_MODULATEINVALPHA_ADDCOLOR + | D3DTEXOPCAPS_MODULATEINVCOLOR_ADDALPHA + | D3DTEXOPCAPS_PREMODULATE + | D3DTEXOPCAPS_SELECTARG1 + | D3DTEXOPCAPS_SELECTARG2 + | D3DTEXOPCAPS_SUBTRACT; + + desc.wMaxTextureBlendStages = ddrawCaps::MaxTextureBlendStages; + desc.wMaxSimultaneousTextures = ddrawCaps::MaxSimultaneousTextures; + + return desc; + } + + inline D3DDEVICEDESC7 GetD3D7Caps(const IID rclsid, const D3DOptions* options) { + D3DDEVICEDESC7 desc7; + + desc7.dwDevCaps = D3DDEVCAPS_CANBLTSYSTONONLOCAL + | D3DDEVCAPS_CANRENDERAFTERFLIP + | D3DDEVCAPS_DRAWPRIMTLVERTEX + | D3DDEVCAPS_EXECUTESYSTEMMEMORY + | D3DDEVCAPS_EXECUTEVIDEOMEMORY + | D3DDEVCAPS_FLOATTLVERTEX + // | D3DDEVCAPS_HWRASTERIZATION + // | D3DDEVCAPS_HWTRANSFORMANDLIGHT + // | D3DDEVCAPS_DRAWPRIMITIVES2 + // | D3DDEVCAPS_SEPARATETEXTUREMEMORIES + // | D3DDEVCAPS_DRAWPRIMITIVES2EX + // | D3DDEVCAPS_SORTDECREASINGZ + // | D3DDEVCAPS_SORTEXACT + // | D3DDEVCAPS_SORTINCREASINGZ + // | D3DDEVCAPS_STRIDEDVERTICES // Mentioned in the docs, but apparently is a ghost + | D3DDEVCAPS_TEXTURENONLOCALVIDMEM + // | D3DDEVCAPS_TEXTURESYSTEMMEMORY + | D3DDEVCAPS_TEXTUREVIDEOMEMORY + | D3DDEVCAPS_TLVERTEXSYSTEMMEMORY + | D3DDEVCAPS_TLVERTEXVIDEOMEMORY; + + if (rclsid == IID_IDirect3DTnLHalDevice) { + desc7.dwDevCaps |= D3DDEVCAPS_HWRASTERIZATION + | D3DDEVCAPS_HWTRANSFORMANDLIGHT + | D3DDEVCAPS_DRAWPRIMITIVES2 + | D3DDEVCAPS_DRAWPRIMITIVES2EX; + } + else if (rclsid == IID_IDirect3DHALDevice) { + desc7.dwDevCaps |= D3DDEVCAPS_HWRASTERIZATION + | D3DDEVCAPS_DRAWPRIMITIVES2 + | D3DDEVCAPS_DRAWPRIMITIVES2EX; + } + + D3DPRIMCAPS prim; + prim.dwSize = sizeof(D3DPRIMCAPS); + + prim.dwMiscCaps = D3DPMISCCAPS_CULLCCW + | D3DPMISCCAPS_CULLCW + | D3DPMISCCAPS_CULLNONE + // | D3DPMISCCAPS_CONFORMANT + // | D3DPMISCCAPS_LINEPATTERNREP // Not implemented in D3D9 + // | D3DPMISCCAPS_MASKPLANES + | D3DPMISCCAPS_MASKZ; + + prim.dwRasterCaps = D3DPRASTERCAPS_ANISOTROPY + | D3DPRASTERCAPS_ANTIALIASEDGES // Technically not implemented in D3D9 + // | D3DPRASTERCAPS_ANTIALIASSORTDEPENDENT + // | D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT + | D3DPRASTERCAPS_DITHER + | D3DPRASTERCAPS_FOGRANGE + | D3DPRASTERCAPS_FOGTABLE + | D3DPRASTERCAPS_FOGVERTEX + | D3DPRASTERCAPS_MIPMAPLODBIAS + // | D3DPRASTERCAPS_PAT // Not implemented in D3D9 + // | D3DPRASTERCAPS_ROP2 + | D3DPRASTERCAPS_STIPPLE // Technically not implemented + | D3DPRASTERCAPS_SUBPIXEL + // | D3DPRASTERCAPS_SUBPIXELX + // | D3DPRASTERCAPS_TRANSLUCENTSORTINDEPENDENT + // | D3DPRASTERCAPS_WBUFFER + | D3DPRASTERCAPS_WFOG + // | D3DPRASTERCAPS_XOR + | D3DPRASTERCAPS_ZBIAS + // | D3DPRASTERCAPS_ZBUFFERLESSHSR // Easy footgun to not get a z-buffer + | D3DPRASTERCAPS_ZFOG + | D3DPRASTERCAPS_ZTEST; + + if (unlikely(options->emulateFSAA != FSAAEmulation::Disabled)) { + prim.dwRasterCaps |= D3DPRASTERCAPS_ANTIALIASSORTDEPENDENT + | D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT; + } + + prim.dwZCmpCaps = D3DPCMPCAPS_ALWAYS + | D3DPCMPCAPS_EQUAL + | D3DPCMPCAPS_GREATER + | D3DPCMPCAPS_GREATEREQUAL + | D3DPCMPCAPS_LESS + | D3DPCMPCAPS_LESSEQUAL + | D3DPCMPCAPS_NEVER + | D3DPCMPCAPS_NOTEQUAL; + + prim.dwSrcBlendCaps = D3DPBLENDCAPS_BOTHINVSRCALPHA + | D3DPBLENDCAPS_BOTHSRCALPHA + | D3DPBLENDCAPS_DESTALPHA + | D3DPBLENDCAPS_DESTCOLOR + | D3DPBLENDCAPS_INVDESTALPHA + | D3DPBLENDCAPS_INVDESTCOLOR + | D3DPBLENDCAPS_INVSRCALPHA + | D3DPBLENDCAPS_INVSRCCOLOR + | D3DPBLENDCAPS_ONE + | D3DPBLENDCAPS_SRCALPHA + | D3DPBLENDCAPS_SRCALPHASAT + | D3DPBLENDCAPS_SRCCOLOR + | D3DPBLENDCAPS_ZERO; + + prim.dwDestBlendCaps = prim.dwSrcBlendCaps; + + prim.dwAlphaCmpCaps = prim.dwZCmpCaps; + + prim.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND + // | D3DPSHADECAPS_ALPHAFLATSTIPPLED + | D3DPSHADECAPS_ALPHAGOURAUDBLEND + // | D3DPSHADECAPS_ALPHAGOURAUDSTIPPLED + // | D3DPSHADECAPS_ALPHAPHONGBLEND + // | D3DPSHADECAPS_ALPHAPHONGSTIPPLED + // | D3DPSHADECAPS_COLORFLATMONO + | D3DPSHADECAPS_COLORFLATRGB + // | D3DPSHADECAPS_COLORGOURAUDMONO + | D3DPSHADECAPS_COLORGOURAUDRGB + // | D3DPSHADECAPS_COLORPHONGMONO + // | D3DPSHADECAPS_COLORPHONGRGB + | D3DPSHADECAPS_FOGFLAT + | D3DPSHADECAPS_FOGGOURAUD + // | D3DPSHADECAPS_FOGPHONG + // | D3DPSHADECAPS_SPECULARFLATMONO + | D3DPSHADECAPS_SPECULARFLATRGB + // | D3DPSHADECAPS_SPECULARGOURAUDMONO + | D3DPSHADECAPS_SPECULARGOURAUDRGB; + // | D3DPSHADECAPS_SPECULARPHONGMONO + // | D3DPSHADECAPS_SPECULARPHONGRGB; + + prim.dwTextureCaps = D3DPTEXTURECAPS_ALPHA + | D3DPTEXTURECAPS_ALPHAPALETTE + | D3DPTEXTURECAPS_BORDER + // | D3DPTEXTURECAPS_COLORKEYBLEND + | D3DPTEXTURECAPS_CUBEMAP + | D3DPTEXTURECAPS_PERSPECTIVE + | D3DPTEXTURECAPS_PROJECTED + // | D3DPTEXTURECAPS_SQUAREONLY + | D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE + | D3DPTEXTURECAPS_TRANSPARENCY; + + if (unlikely(options->forcePOW2Textures)) { + prim.dwTextureCaps |= D3DPTEXTURECAPS_NONPOW2CONDITIONAL + | D3DPTEXTURECAPS_POW2; + } + + prim.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR + | D3DPTFILTERCAPS_LINEARMIPLINEAR + | D3DPTFILTERCAPS_LINEARMIPNEAREST + | D3DPTFILTERCAPS_MIPLINEAR + | D3DPTFILTERCAPS_MIPNEAREST + | D3DPTFILTERCAPS_NEAREST + // | D3DPTFILTERCAPS_MAGFAFLATCUBIC + | D3DPTFILTERCAPS_MAGFANISOTROPIC + // | D3DPTFILTERCAPS_MAGFGAUSSIANCUBIC + | D3DPTFILTERCAPS_MAGFLINEAR + | D3DPTFILTERCAPS_MAGFPOINT + | D3DPTFILTERCAPS_MINFANISOTROPIC + | D3DPTFILTERCAPS_MINFLINEAR + | D3DPTFILTERCAPS_MINFPOINT + | D3DPTFILTERCAPS_MIPFLINEAR + | D3DPTFILTERCAPS_MIPFPOINT; + + // Allegedly a deprecated item, but some d3d7 games, + // like Summoner, still expect it to contain legacy values + prim.dwTextureBlendCaps = D3DPTBLENDCAPS_ADD + | D3DPTBLENDCAPS_COPY + | D3DPTBLENDCAPS_DECAL + | D3DPTBLENDCAPS_DECALALPHA + | D3DPTBLENDCAPS_DECALMASK + | D3DPTBLENDCAPS_MODULATE + | D3DPTBLENDCAPS_MODULATEALPHA + | D3DPTBLENDCAPS_MODULATEMASK; + + prim.dwTextureAddressCaps = D3DPTADDRESSCAPS_BORDER + | D3DPTADDRESSCAPS_CLAMP + | D3DPTADDRESSCAPS_INDEPENDENTUV + | D3DPTADDRESSCAPS_MIRROR + | D3DPTADDRESSCAPS_WRAP; + + prim.dwStippleWidth = 32; + prim.dwStippleHeight = 32; + + desc7.dpcLineCaps = prim; + desc7.dpcTriCaps = prim; + + desc7.dwDeviceRenderBitDepth = DDBD_16 | DDBD_24 | DDBD_32; + desc7.dwDeviceZBufferBitDepth = options->supportD16 ? DDBD_16 | DDBD_24 : DDBD_24; + desc7.dwMinTextureWidth = 1; + desc7.dwMinTextureHeight = 1; + desc7.dwMaxTextureWidth = ddrawCaps::MaxTextureDimension; + desc7.dwMaxTextureHeight = ddrawCaps::MaxTextureDimension; + desc7.dwMaxTextureRepeat = 8192; + desc7.dwMaxTextureAspectRatio = 8192; + desc7.dwMaxAnisotropy = 16; + desc7.dvGuardBandLeft = -32768.0f; + desc7.dvGuardBandTop = -32768.0f; + desc7.dvGuardBandRight = 32768.0f; + desc7.dvGuardBandBottom = 32768.0f; + desc7.dvExtentsAdjust = 0.0f; + + desc7.dwStencilCaps = D3DSTENCILCAPS_DECR + | D3DSTENCILCAPS_DECRSAT + | D3DSTENCILCAPS_INCR + | D3DSTENCILCAPS_INCRSAT + | D3DSTENCILCAPS_INVERT + | D3DSTENCILCAPS_KEEP + | D3DSTENCILCAPS_REPLACE + | D3DSTENCILCAPS_ZERO; + + desc7.dwFVFCaps = (ddrawCaps::MaxSimultaneousTextures & D3DFVFCAPS_TEXCOORDCOUNTMASK); + // | D3DFVFCAPS_DONOTSTRIPELEMENTS; + + desc7.dwTextureOpCaps = D3DTEXOPCAPS_ADD + | D3DTEXOPCAPS_ADDSIGNED + | D3DTEXOPCAPS_ADDSIGNED2X + | D3DTEXOPCAPS_ADDSMOOTH + | D3DTEXOPCAPS_BLENDCURRENTALPHA + | D3DTEXOPCAPS_BLENDDIFFUSEALPHA + | D3DTEXOPCAPS_BLENDFACTORALPHA + | D3DTEXOPCAPS_BLENDTEXTUREALPHA + | D3DTEXOPCAPS_BLENDTEXTUREALPHAPM + | D3DTEXOPCAPS_BUMPENVMAP + | D3DTEXOPCAPS_BUMPENVMAPLUMINANCE + | D3DTEXOPCAPS_DISABLE + | D3DTEXOPCAPS_DOTPRODUCT3 + | D3DTEXOPCAPS_MODULATE + | D3DTEXOPCAPS_MODULATE2X + | D3DTEXOPCAPS_MODULATE4X + | D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR + | D3DTEXOPCAPS_MODULATECOLOR_ADDALPHA + | D3DTEXOPCAPS_MODULATEINVALPHA_ADDCOLOR + | D3DTEXOPCAPS_MODULATEINVCOLOR_ADDALPHA + | D3DTEXOPCAPS_PREMODULATE + | D3DTEXOPCAPS_SELECTARG1 + | D3DTEXOPCAPS_SELECTARG2 + | D3DTEXOPCAPS_SUBTRACT; + + desc7.wMaxTextureBlendStages = ddrawCaps::MaxTextureBlendStages; + desc7.wMaxSimultaneousTextures = ddrawCaps::MaxSimultaneousTextures; + desc7.dwMaxActiveLights = ddrawCaps::MaxEnabledLights; + desc7.dvMaxVertexW = 1e10f; + + desc7.deviceGUID = rclsid; + + desc7.wMaxUserClipPlanes = ddrawCaps::MaxClipPlanes; + desc7.wMaxVertexBlendMatrices = 4; + + desc7.dwVertexProcessingCaps = D3DVTXPCAPS_TEXGEN + | D3DVTXPCAPS_MATERIALSOURCE7 + | D3DVTXPCAPS_VERTEXFOG + | D3DVTXPCAPS_DIRECTIONALLIGHTS + | D3DVTXPCAPS_POSITIONALLIGHTS + | D3DVTXPCAPS_LOCALVIEWER; + + desc7.dwReserved1 = 0; + desc7.dwReserved2 = 0; + desc7.dwReserved3 = 0; + desc7.dwReserved4 = 0; + + return desc7; + } + + inline bool IsValidD3D3RenderStateType(D3DRENDERSTATETYPE rs) { + return rs == D3DRENDERSTATE_TEXTUREHANDLE + || rs == D3DRENDERSTATE_ANTIALIAS + || rs == D3DRENDERSTATE_TEXTUREADDRESS + || rs == D3DRENDERSTATE_TEXTUREPERSPECTIVE + || rs == D3DRENDERSTATE_WRAPU + || rs == D3DRENDERSTATE_WRAPV + || rs == D3DRENDERSTATE_ZENABLE + || rs == D3DRENDERSTATE_FILLMODE + || rs == D3DRENDERSTATE_SHADEMODE + || rs == D3DRENDERSTATE_LINEPATTERN + || rs == D3DRENDERSTATE_MONOENABLE + || rs == D3DRENDERSTATE_ROP2 + || rs == D3DRENDERSTATE_PLANEMASK + || rs == D3DRENDERSTATE_ZWRITEENABLE + || rs == D3DRENDERSTATE_ALPHATESTENABLE + || rs == D3DRENDERSTATE_LASTPIXEL + || rs == D3DRENDERSTATE_TEXTUREMAG + || rs == D3DRENDERSTATE_TEXTUREMIN + || rs == D3DRENDERSTATE_SRCBLEND + || rs == D3DRENDERSTATE_DESTBLEND + || rs == D3DRENDERSTATE_TEXTUREMAPBLEND + || rs == D3DRENDERSTATE_CULLMODE + || rs == D3DRENDERSTATE_ZFUNC + || rs == D3DRENDERSTATE_ALPHAREF + || rs == D3DRENDERSTATE_ALPHAFUNC + || rs == D3DRENDERSTATE_DITHERENABLE + || rs == D3DRENDERSTATE_BLENDENABLE // The actual D3DRENDERSTATE_ALPHABLENDENABLE + || rs == D3DRENDERSTATE_FOGENABLE + || rs == D3DRENDERSTATE_SPECULARENABLE + || rs == D3DRENDERSTATE_ZVISIBLE + || rs == D3DRENDERSTATE_SUBPIXEL + || rs == D3DRENDERSTATE_SUBPIXELX + || rs == D3DRENDERSTATE_STIPPLEDALPHA + || rs == D3DRENDERSTATE_FOGCOLOR + || rs == D3DRENDERSTATE_FOGTABLEMODE + || rs == D3DRENDERSTATE_FOGTABLESTART + || rs == D3DRENDERSTATE_FOGTABLEEND + || rs == D3DRENDERSTATE_FOGTABLEDENSITY + || rs == D3DRENDERSTATE_STIPPLEENABLE + || rs == D3DRENDERSTATE_STIPPLEPATTERN00 + || rs == D3DRENDERSTATE_STIPPLEPATTERN01 + || rs == D3DRENDERSTATE_STIPPLEPATTERN02 + || rs == D3DRENDERSTATE_STIPPLEPATTERN03 + || rs == D3DRENDERSTATE_STIPPLEPATTERN04 + || rs == D3DRENDERSTATE_STIPPLEPATTERN05 + || rs == D3DRENDERSTATE_STIPPLEPATTERN06 + || rs == D3DRENDERSTATE_STIPPLEPATTERN07 + || rs == D3DRENDERSTATE_STIPPLEPATTERN08 + || rs == D3DRENDERSTATE_STIPPLEPATTERN09 + || rs == D3DRENDERSTATE_STIPPLEPATTERN10 + || rs == D3DRENDERSTATE_STIPPLEPATTERN11 + || rs == D3DRENDERSTATE_STIPPLEPATTERN12 + || rs == D3DRENDERSTATE_STIPPLEPATTERN13 + || rs == D3DRENDERSTATE_STIPPLEPATTERN14 + || rs == D3DRENDERSTATE_STIPPLEPATTERN15 + || rs == D3DRENDERSTATE_STIPPLEPATTERN16 + || rs == D3DRENDERSTATE_STIPPLEPATTERN17 + || rs == D3DRENDERSTATE_STIPPLEPATTERN18 + || rs == D3DRENDERSTATE_STIPPLEPATTERN19 + || rs == D3DRENDERSTATE_STIPPLEPATTERN20 + || rs == D3DRENDERSTATE_STIPPLEPATTERN21 + || rs == D3DRENDERSTATE_STIPPLEPATTERN22 + || rs == D3DRENDERSTATE_STIPPLEPATTERN23 + || rs == D3DRENDERSTATE_STIPPLEPATTERN24 + || rs == D3DRENDERSTATE_STIPPLEPATTERN25 + || rs == D3DRENDERSTATE_STIPPLEPATTERN26 + || rs == D3DRENDERSTATE_STIPPLEPATTERN27 + || rs == D3DRENDERSTATE_STIPPLEPATTERN28 + || rs == D3DRENDERSTATE_STIPPLEPATTERN29 + || rs == D3DRENDERSTATE_STIPPLEPATTERN30 + || rs == D3DRENDERSTATE_STIPPLEPATTERN31; + } + + inline bool IsValidD3D5RenderStateType(D3DRENDERSTATETYPE rs) { + return rs == D3DRENDERSTATE_TEXTUREHANDLE + || rs == D3DRENDERSTATE_ANTIALIAS + || rs == D3DRENDERSTATE_TEXTUREADDRESS + || rs == D3DRENDERSTATE_TEXTUREPERSPECTIVE + || rs == D3DRENDERSTATE_WRAPU + || rs == D3DRENDERSTATE_WRAPV + || rs == D3DRENDERSTATE_ZENABLE + || rs == D3DRENDERSTATE_FILLMODE + || rs == D3DRENDERSTATE_SHADEMODE + || rs == D3DRENDERSTATE_LINEPATTERN + || rs == D3DRENDERSTATE_MONOENABLE + || rs == D3DRENDERSTATE_ROP2 + || rs == D3DRENDERSTATE_PLANEMASK + || rs == D3DRENDERSTATE_ZWRITEENABLE + || rs == D3DRENDERSTATE_ALPHATESTENABLE + || rs == D3DRENDERSTATE_LASTPIXEL + || rs == D3DRENDERSTATE_TEXTUREMAG + || rs == D3DRENDERSTATE_TEXTUREMIN + || rs == D3DRENDERSTATE_SRCBLEND + || rs == D3DRENDERSTATE_DESTBLEND + || rs == D3DRENDERSTATE_TEXTUREMAPBLEND + || rs == D3DRENDERSTATE_CULLMODE + || rs == D3DRENDERSTATE_ZFUNC + || rs == D3DRENDERSTATE_ALPHAREF + || rs == D3DRENDERSTATE_ALPHAFUNC + || rs == D3DRENDERSTATE_DITHERENABLE + || rs == D3DRENDERSTATE_BLENDENABLE // The actual D3DRENDERSTATE_ALPHABLENDENABLE + || rs == D3DRENDERSTATE_FOGENABLE + || rs == D3DRENDERSTATE_SPECULARENABLE + || rs == D3DRENDERSTATE_ZVISIBLE + || rs == D3DRENDERSTATE_SUBPIXEL + || rs == D3DRENDERSTATE_SUBPIXELX + || rs == D3DRENDERSTATE_STIPPLEDALPHA + || rs == D3DRENDERSTATE_FOGCOLOR + || rs == D3DRENDERSTATE_FOGTABLEMODE + || rs == D3DRENDERSTATE_FOGTABLESTART + || rs == D3DRENDERSTATE_FOGTABLEEND + || rs == D3DRENDERSTATE_FOGTABLEDENSITY + || rs == D3DRENDERSTATE_STIPPLEENABLE + || rs == D3DRENDERSTATE_EDGEANTIALIAS + || rs == D3DRENDERSTATE_COLORKEYENABLE + || rs == D3DRENDERSTATE_ALPHABLENDENABLE_OLD // Deprecated in D3D6 + || rs == D3DRENDERSTATE_BORDERCOLOR + || rs == D3DRENDERSTATE_TEXTUREADDRESSU + || rs == D3DRENDERSTATE_TEXTUREADDRESSV + || rs == D3DRENDERSTATE_MIPMAPLODBIAS + || rs == D3DRENDERSTATE_ZBIAS + || rs == D3DRENDERSTATE_RANGEFOGENABLE + || rs == D3DRENDERSTATE_ANISOTROPY + || rs == D3DRENDERSTATE_FLUSHBATCH // Not in the docs, but valid in D3D5 + || rs == D3DRENDERSTATE_STIPPLEPATTERN00 + || rs == D3DRENDERSTATE_STIPPLEPATTERN01 + || rs == D3DRENDERSTATE_STIPPLEPATTERN02 + || rs == D3DRENDERSTATE_STIPPLEPATTERN03 + || rs == D3DRENDERSTATE_STIPPLEPATTERN04 + || rs == D3DRENDERSTATE_STIPPLEPATTERN05 + || rs == D3DRENDERSTATE_STIPPLEPATTERN06 + || rs == D3DRENDERSTATE_STIPPLEPATTERN07 + || rs == D3DRENDERSTATE_STIPPLEPATTERN08 + || rs == D3DRENDERSTATE_STIPPLEPATTERN09 + || rs == D3DRENDERSTATE_STIPPLEPATTERN10 + || rs == D3DRENDERSTATE_STIPPLEPATTERN11 + || rs == D3DRENDERSTATE_STIPPLEPATTERN12 + || rs == D3DRENDERSTATE_STIPPLEPATTERN13 + || rs == D3DRENDERSTATE_STIPPLEPATTERN14 + || rs == D3DRENDERSTATE_STIPPLEPATTERN15 + || rs == D3DRENDERSTATE_STIPPLEPATTERN16 + || rs == D3DRENDERSTATE_STIPPLEPATTERN17 + || rs == D3DRENDERSTATE_STIPPLEPATTERN18 + || rs == D3DRENDERSTATE_STIPPLEPATTERN19 + || rs == D3DRENDERSTATE_STIPPLEPATTERN20 + || rs == D3DRENDERSTATE_STIPPLEPATTERN21 + || rs == D3DRENDERSTATE_STIPPLEPATTERN22 + || rs == D3DRENDERSTATE_STIPPLEPATTERN23 + || rs == D3DRENDERSTATE_STIPPLEPATTERN24 + || rs == D3DRENDERSTATE_STIPPLEPATTERN25 + || rs == D3DRENDERSTATE_STIPPLEPATTERN26 + || rs == D3DRENDERSTATE_STIPPLEPATTERN27 + || rs == D3DRENDERSTATE_STIPPLEPATTERN28 + || rs == D3DRENDERSTATE_STIPPLEPATTERN29 + || rs == D3DRENDERSTATE_STIPPLEPATTERN30 + || rs == D3DRENDERSTATE_STIPPLEPATTERN31; + } + + inline bool IsValidD3D6RenderStateType(D3DRENDERSTATETYPE rs) { + return rs == D3DRENDERSTATE_TEXTUREHANDLE + || rs == D3DRENDERSTATE_ANTIALIAS + || rs == D3DRENDERSTATE_TEXTUREADDRESS + || rs == D3DRENDERSTATE_TEXTUREPERSPECTIVE + || rs == D3DRENDERSTATE_WRAPU + || rs == D3DRENDERSTATE_WRAPV + || rs == D3DRENDERSTATE_ZENABLE + || rs == D3DRENDERSTATE_FILLMODE + || rs == D3DRENDERSTATE_SHADEMODE + || rs == D3DRENDERSTATE_LINEPATTERN + || rs == D3DRENDERSTATE_MONOENABLE + || rs == D3DRENDERSTATE_ROP2 + || rs == D3DRENDERSTATE_PLANEMASK + || rs == D3DRENDERSTATE_ZWRITEENABLE + || rs == D3DRENDERSTATE_ALPHATESTENABLE + || rs == D3DRENDERSTATE_LASTPIXEL + || rs == D3DRENDERSTATE_TEXTUREMAG + || rs == D3DRENDERSTATE_TEXTUREMIN + || rs == D3DRENDERSTATE_SRCBLEND + || rs == D3DRENDERSTATE_DESTBLEND + || rs == D3DRENDERSTATE_TEXTUREMAPBLEND + || rs == D3DRENDERSTATE_CULLMODE + || rs == D3DRENDERSTATE_ZFUNC + || rs == D3DRENDERSTATE_ALPHAREF + || rs == D3DRENDERSTATE_ALPHAFUNC + || rs == D3DRENDERSTATE_DITHERENABLE + || rs == D3DRENDERSTATE_ALPHABLENDENABLE + || rs == D3DRENDERSTATE_FOGENABLE + || rs == D3DRENDERSTATE_SPECULARENABLE + || rs == D3DRENDERSTATE_ZVISIBLE + || rs == D3DRENDERSTATE_SUBPIXEL + || rs == D3DRENDERSTATE_SUBPIXELX + || rs == D3DRENDERSTATE_STIPPLEDALPHA + || rs == D3DRENDERSTATE_FOGCOLOR + || rs == D3DRENDERSTATE_FOGTABLEMODE + || rs == D3DRENDERSTATE_FOGTABLESTART + || rs == D3DRENDERSTATE_FOGTABLEEND + || rs == D3DRENDERSTATE_FOGTABLEDENSITY + || rs == D3DRENDERSTATE_STIPPLEENABLE + || rs == D3DRENDERSTATE_EDGEANTIALIAS + || rs == D3DRENDERSTATE_COLORKEYENABLE + || rs == D3DRENDERSTATE_BORDERCOLOR + || rs == D3DRENDERSTATE_TEXTUREADDRESSU + || rs == D3DRENDERSTATE_TEXTUREADDRESSV + || rs == D3DRENDERSTATE_MIPMAPLODBIAS + || rs == D3DRENDERSTATE_ZBIAS + || rs == D3DRENDERSTATE_RANGEFOGENABLE + || rs == D3DRENDERSTATE_ANISOTROPY + || rs == D3DRENDERSTATE_FLUSHBATCH + || rs == D3DRENDERSTATE_TRANSLUCENTSORTINDEPENDENT + || rs == D3DRENDERSTATE_STENCILENABLE + || rs == D3DRENDERSTATE_STENCILFAIL + || rs == D3DRENDERSTATE_STENCILZFAIL + || rs == D3DRENDERSTATE_STENCILPASS + || rs == D3DRENDERSTATE_STENCILFUNC + || rs == D3DRENDERSTATE_STENCILREF + || rs == D3DRENDERSTATE_STENCILMASK + || rs == D3DRENDERSTATE_STENCILWRITEMASK + || rs == D3DRENDERSTATE_TEXTUREFACTOR + || rs == D3DRENDERSTATE_STIPPLEPATTERN00 + || rs == D3DRENDERSTATE_STIPPLEPATTERN01 + || rs == D3DRENDERSTATE_STIPPLEPATTERN02 + || rs == D3DRENDERSTATE_STIPPLEPATTERN03 + || rs == D3DRENDERSTATE_STIPPLEPATTERN04 + || rs == D3DRENDERSTATE_STIPPLEPATTERN05 + || rs == D3DRENDERSTATE_STIPPLEPATTERN06 + || rs == D3DRENDERSTATE_STIPPLEPATTERN07 + || rs == D3DRENDERSTATE_STIPPLEPATTERN08 + || rs == D3DRENDERSTATE_STIPPLEPATTERN09 + || rs == D3DRENDERSTATE_STIPPLEPATTERN10 + || rs == D3DRENDERSTATE_STIPPLEPATTERN11 + || rs == D3DRENDERSTATE_STIPPLEPATTERN12 + || rs == D3DRENDERSTATE_STIPPLEPATTERN13 + || rs == D3DRENDERSTATE_STIPPLEPATTERN14 + || rs == D3DRENDERSTATE_STIPPLEPATTERN15 + || rs == D3DRENDERSTATE_STIPPLEPATTERN16 + || rs == D3DRENDERSTATE_STIPPLEPATTERN17 + || rs == D3DRENDERSTATE_STIPPLEPATTERN18 + || rs == D3DRENDERSTATE_STIPPLEPATTERN19 + || rs == D3DRENDERSTATE_STIPPLEPATTERN20 + || rs == D3DRENDERSTATE_STIPPLEPATTERN21 + || rs == D3DRENDERSTATE_STIPPLEPATTERN22 + || rs == D3DRENDERSTATE_STIPPLEPATTERN23 + || rs == D3DRENDERSTATE_STIPPLEPATTERN24 + || rs == D3DRENDERSTATE_STIPPLEPATTERN25 + || rs == D3DRENDERSTATE_STIPPLEPATTERN26 + || rs == D3DRENDERSTATE_STIPPLEPATTERN27 + || rs == D3DRENDERSTATE_STIPPLEPATTERN28 + || rs == D3DRENDERSTATE_STIPPLEPATTERN29 + || rs == D3DRENDERSTATE_STIPPLEPATTERN30 + || rs == D3DRENDERSTATE_STIPPLEPATTERN31 + || rs == D3DRENDERSTATE_WRAP0 + || rs == D3DRENDERSTATE_WRAP1 + || rs == D3DRENDERSTATE_WRAP2 + || rs == D3DRENDERSTATE_WRAP3 + || rs == D3DRENDERSTATE_WRAP4 + || rs == D3DRENDERSTATE_WRAP5 + || rs == D3DRENDERSTATE_WRAP6 + || rs == D3DRENDERSTATE_WRAP7; + } + + inline bool IsValidD3D7RenderStateType(D3DRENDERSTATETYPE rs) { + return rs == D3DRENDERSTATE_ANTIALIAS + || rs == D3DRENDERSTATE_TEXTUREPERSPECTIVE + || rs == D3DRENDERSTATE_ZENABLE + || rs == D3DRENDERSTATE_FILLMODE + || rs == D3DRENDERSTATE_SHADEMODE + || rs == D3DRENDERSTATE_LINEPATTERN + || rs == D3DRENDERSTATE_ZWRITEENABLE + || rs == D3DRENDERSTATE_ALPHATESTENABLE + || rs == D3DRENDERSTATE_LASTPIXEL + || rs == D3DRENDERSTATE_SRCBLEND + || rs == D3DRENDERSTATE_DESTBLEND + || rs == D3DRENDERSTATE_CULLMODE + || rs == D3DRENDERSTATE_ZFUNC + || rs == D3DRENDERSTATE_ALPHAREF + || rs == D3DRENDERSTATE_ALPHAFUNC + || rs == D3DRENDERSTATE_DITHERENABLE + || rs == D3DRENDERSTATE_ALPHABLENDENABLE + || rs == D3DRENDERSTATE_FOGENABLE + || rs == D3DRENDERSTATE_SPECULARENABLE + || rs == D3DRENDERSTATE_ZVISIBLE + || rs == D3DRENDERSTATE_STIPPLEDALPHA + || rs == D3DRENDERSTATE_FOGCOLOR + || rs == D3DRENDERSTATE_FOGTABLEMODE + || rs == D3DRENDERSTATE_FOGTABLESTART + || rs == D3DRENDERSTATE_FOGTABLEEND + || rs == D3DRENDERSTATE_FOGTABLEDENSITY + || rs == D3DRENDERSTATE_FOGSTART + || rs == D3DRENDERSTATE_FOGEND + || rs == D3DRENDERSTATE_FOGDENSITY + || rs == D3DRENDERSTATE_EDGEANTIALIAS + || rs == D3DRENDERSTATE_COLORKEYENABLE + || rs == D3DRENDERSTATE_ZBIAS + || rs == D3DRENDERSTATE_RANGEFOGENABLE + || rs == D3DRENDERSTATE_STENCILENABLE + || rs == D3DRENDERSTATE_STENCILFAIL + || rs == D3DRENDERSTATE_STENCILZFAIL + || rs == D3DRENDERSTATE_STENCILPASS + || rs == D3DRENDERSTATE_STENCILFUNC + || rs == D3DRENDERSTATE_STENCILREF + || rs == D3DRENDERSTATE_STENCILMASK + || rs == D3DRENDERSTATE_STENCILWRITEMASK + || rs == D3DRENDERSTATE_TEXTUREFACTOR + || rs == D3DRENDERSTATE_WRAP0 + || rs == D3DRENDERSTATE_WRAP1 + || rs == D3DRENDERSTATE_WRAP2 + || rs == D3DRENDERSTATE_WRAP3 + || rs == D3DRENDERSTATE_WRAP4 + || rs == D3DRENDERSTATE_WRAP5 + || rs == D3DRENDERSTATE_WRAP6 + || rs == D3DRENDERSTATE_WRAP7 + || rs == D3DRENDERSTATE_CLIPPING + || rs == D3DRENDERSTATE_LIGHTING + || rs == D3DRENDERSTATE_EXTENTS + || rs == D3DRENDERSTATE_AMBIENT + || rs == D3DRENDERSTATE_FOGVERTEXMODE + || rs == D3DRENDERSTATE_COLORVERTEX + || rs == D3DRENDERSTATE_LOCALVIEWER + || rs == D3DRENDERSTATE_NORMALIZENORMALS + || rs == D3DRENDERSTATE_COLORKEYBLENDENABLE + || rs == D3DRENDERSTATE_DIFFUSEMATERIALSOURCE + || rs == D3DRENDERSTATE_SPECULARMATERIALSOURCE + || rs == D3DRENDERSTATE_AMBIENTMATERIALSOURCE + || rs == D3DRENDERSTATE_EMISSIVEMATERIALSOURCE + || rs == D3DRENDERSTATE_VERTEXBLEND + || rs == D3DRENDERSTATE_CLIPPLANEENABLE; + } + +} \ No newline at end of file diff --git a/src/ddraw/ddraw_wrapped_object.h b/src/ddraw/ddraw_wrapped_object.h new file mode 100644 index 00000000000..cd2ded62ddb --- /dev/null +++ b/src/ddraw/ddraw_wrapped_object.h @@ -0,0 +1,95 @@ +#pragma once + +#include "ddraw_include.h" + +namespace dxvk { + + template + class DDrawWrappedObject : public ComObjectClamp { + + public: + + using Parent = ParentType; + using DDraw = DDrawType; + using D3D9 = D3D9Type; + + DDrawWrappedObject(Parent* parent, Com&& proxiedIntf, Com&& object) + : m_parent ( parent ) + , m_proxy ( std::move(proxiedIntf) ) + , m_d3d9 ( std::move(object) ) { + } + + void UpdateParent(Parent* parent) { + m_parent = parent; + } + + Parent* GetParent() const { + return m_parent; + } + + DDraw* GetProxied() const { + return m_proxy.ptr(); + } + + D3D9* GetD3D9() const { + return m_d3d9.ptr(); + } + + void SetD3D9(Com&& object) { + m_d3d9 = std::move(object); + } + + bool IsInitialized() const { + return m_d3d9 != nullptr; + } + + // For cases where the object may be null. + static D3D9* GetD3D9Nullable(DDrawWrappedObject* self) { + if (unlikely(self == NULL)) { + return NULL; + } + return self->m_d3d9.ptr(); + } + + template + static D3D9* GetD3D9Nullable(Com& self) { + return GetD3D9Nullable(self.ptr()); + } + + IUnknown* GetInterface(REFIID riid) { + if (riid == __uuidof(IUnknown)) + return this; + if (riid == __uuidof(DDraw)) + return this; + + throw DxvkError("DDrawWrappedObject::QueryInterface: Unknown interface query"); + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { + Logger::debug(">>> DDrawWrappedObject::QueryInterface"); + + if (unlikely(ppvObject == nullptr)) + return E_POINTER; + + InitReturnPtr(ppvObject); + + try { + *ppvObject = ref(this->GetInterface(riid)); + return S_OK; + } catch (const DxvkError& e) { + Logger::warn(e.message()); + Logger::warn(str::format(riid)); + return E_NOINTERFACE; + } + } + + protected: + + Parent* m_parent = nullptr; + + Com m_proxy; + Com m_d3d9; + + }; + +} \ No newline at end of file diff --git a/src/ddraw/meson.build b/src/ddraw/meson.build new file mode 100644 index 00000000000..7614d84778a --- /dev/null +++ b/src/ddraw/meson.build @@ -0,0 +1,61 @@ +ddraw_res = wrc_generator.process('version.rc') + +ddraw_src = [ + 'ddraw_main.cpp', + 'ddraw_class_factory.cpp', + 'ddraw_common_interface.cpp', + 'ddraw_common_surface.cpp', + 'ddraw_clipper.cpp', + 'ddraw_gamma.cpp', + 'ddraw_palette.cpp', + 'ddraw/ddraw_interface.cpp', + 'ddraw/ddraw_surface.cpp', + 'ddraw2/ddraw2_interface.cpp', + 'ddraw2/ddraw2_surface.cpp', + 'ddraw2/ddraw3_surface.cpp', + 'ddraw4/ddraw4_interface.cpp', + 'ddraw4/ddraw4_surface.cpp', + 'ddraw7/ddraw7_interface.cpp', + 'ddraw7/ddraw7_surface.cpp', + 'd3d_common_interface.cpp', + 'd3d_common_material.cpp', + 'd3d_common_texture.cpp', + 'd3d_common_viewport.cpp', + 'd3d_light.cpp', + 'd3d_multithread.cpp', + 'd3d3/d3d3_device.cpp', + 'd3d3/d3d3_execute_buffer.cpp', + 'd3d3/d3d3_interface.cpp', + 'd3d3/d3d3_material.cpp', + 'd3d3/d3d3_texture.cpp', + 'd3d3/d3d3_viewport.cpp', + 'd3d5/d3d5_device.cpp', + 'd3d5/d3d5_interface.cpp', + 'd3d5/d3d5_material.cpp', + 'd3d5/d3d5_texture.cpp', + 'd3d5/d3d5_viewport.cpp', + 'd3d6/d3d6_buffer.cpp', + 'd3d6/d3d6_device.cpp', + 'd3d6/d3d6_interface.cpp', + 'd3d6/d3d6_material.cpp', + 'd3d6/d3d6_texture.cpp', + 'd3d6/d3d6_viewport.cpp', + 'd3d7/d3d7_buffer.cpp', + 'd3d7/d3d7_interface.cpp', + 'd3d7/d3d7_device.cpp', + 'd3d7/d3d7_state_block.cpp', +] + +ddraw_dll = shared_library('ddraw'+dll_ext, ddraw_src, ddraw_res, + name_prefix : '', + dependencies : [ util_dep, dxso_dep, dxvk_dep ], + include_directories : dxvk_include_path, + install : true, + vs_module_defs : 'ddraw'+def_spec_ext, + link_with : [ d3d9_dll ], +) + +ddraw_dep = declare_dependency( + link_with : [ ddraw_dll, d3d9_dll ], + include_directories : [ dxvk_include_path ], +) diff --git a/src/ddraw/version.rc b/src/ddraw/version.rc new file mode 100644 index 00000000000..b17f1a316e9 --- /dev/null +++ b/src/ddraw/version.rc @@ -0,0 +1,31 @@ +#include + +// DLL version information. +VS_VERSION_INFO VERSIONINFO +FILEVERSION 4,07,00,700 +PRODUCTVERSION 4,07,00,700 +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", "DirectDraw Runtime" + VALUE "FileVersion", "4.07.00.0700" + VALUE "InternalName", "DDraw.dll" + VALUE "LegalCopyright", "zlib/libpng license" + VALUE "OriginalFilename", "DDraw.dll" + VALUE "ProductName", "DXVK" + VALUE "ProductVersion", "4.07.00.0700" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0809, 1200 + END +END diff --git a/src/meson.build b/src/meson.build index 6cab08fd9af..3f6ff86fc28 100644 --- a/src/meson.build +++ b/src/meson.build @@ -38,6 +38,13 @@ if get_option('enable_d3d8') endif # Nothing selected -if not get_option('enable_d3d9') and not get_option('enable_d3d10') and not get_option('enable_d3d11') +if get_option('enable_ddraw') + if not get_option('enable_d3d9') + error('D3D9 is required for DDRAW.') + endif + subdir('ddraw') +endif + +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 5623f7328ae..eb8261e7830 100644 --- a/src/util/config/config.cpp +++ b/src/util/config/config.cpp @@ -18,7 +18,7 @@ namespace dxvk { /* D3D11 GAMES */ /**********************************************/ - /* Batman Arkham Knight - doesn't like intel vendor id + /* Batman Arkham Knight - doesn't like intel vendor id (refuses to boot if vendor isn't 0x10de or 0x1002) */ { R"(\\BatmanAK\.exe$)", {{ { "dxgi.hideIntelGpu", "True" }, @@ -36,13 +36,13 @@ namespace dxvk { { R"(\\EliteDangerous64\.exe$)", {{ { "dxgi.customVendorId", "10de" }, }} }, - /* EVE Online: Needs this to expose D3D12 * + /* EVE Online: Needs this to expose D3D12 * * otherwise D3D12 option on launcher is * * greyed out */ { R"(\\evelauncher\.exe$)", {{ { "d3d11.maxFeatureLevel", "12_1" }, }} }, - /* The Evil Within: Submits command lists * + /* The Evil Within: Submits command lists * * multiple times */ { R"(\\EvilWithin(Demo)?\.exe$)", {{ { "d3d11.dcSingleUseMode", "False" }, @@ -62,6 +62,11 @@ namespace dxvk { { "dxgi.hideNvidiaGpu", "False" }, { "dxgi.hideIntelGpu", "True" }, }} }, + /* Far Cry 5 and New Dawn * + * Invisible terrain on Intel */ + { R"(\\FarCry(5|NewDawn)\.exe$)", {{ + { "d3d11.zeroInitWorkgroupMemory", "True" }, + }} }, /* Frostpunk: Renders one frame with D3D9 * * after creating the DXGI swap chain */ { R"(\\Frostpunk\.exe$)", {{ @@ -72,7 +77,7 @@ namespace dxvk { { R"(\\nioh\.exe$)", {{ { "d3d9.deferSurfaceCreation", "True" }, }} }, - /* Quantum Break: Mever initializes shared * + /* Quantum Break: Never initializes shared * * memory in one of its compute shaders. * * Also loves using MAP_WRITE on the same * * set of resources multiple times per frame. */ @@ -90,7 +95,7 @@ namespace dxvk { }} }, /* Fifa '19+: Binds typed buffer SRV to shader * * that expects raw/structured buffer SRV */ - { R"(\\FIFA(19|[2-9][0-9])(_demo)?\.exe$)", {{ + { R"(\\FIFA(19|20|21|22)(_demo)?\.exe$)", {{ { "dxvk.useRawSsbo", "True" }, }} }, /* Resident Evil 2/3: Ignore WaW hazards */ @@ -154,6 +159,16 @@ namespace dxvk { { R"(\\h2m-mod\.exe$)", {{ { "dxgi.customVendorId", "10de" }, }} }, + /* HMW-Mod - Modern Warfare Remastered * + * AMD AGS crash */ + { R"(\\hmw-mod\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* Infinite Warfare * + * AMD AGS crash */ + { R"(\\iw7(_ship|-mod)\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, /* Modern Warfare 2 Campaign Remastered * * AMD AGS crash same as above */ { R"(\\MW2CR\.exe$)", {{ @@ -172,10 +187,11 @@ namespace dxvk { { "d3d11.constantBufferRangeCheck", "True" }, }} }, /* Crysis 3 - slower if it notices AMD card * - * Apitrace mode helps massively in cpu bound * - * game parts */ + * but apparently no longer works when spoofing * + * vendor IDs. Cached dynamic buffers help * + * massively in CPU bound game parts */ { R"(\\Crysis3\.exe$)", {{ - { "dxgi.customVendorId", "10de" }, + { "dxgi.hideNvidiaGpu", "False" }, { "d3d11.cachedDynamicResources", "a" }, }} }, /* Crysis 3 Remastered * @@ -269,14 +285,16 @@ namespace dxvk { /* Crazy Machines 3 - crashes on long device * * descriptions */ { R"(\\cm3\.exe$)", {{ - { "dxgi.customDeviceDesc", "DXVK Adapter" }, + { "dxgi.customDeviceDesc", "DXVK Device" }, }} }, - /* World of Final Fantasy: Broken and useless * - * use of 4x MSAA throughout the renderer */ + /* World of Final Fantasy: Broken and useless use * + * of 4x MSAA throughout the renderer. Water doesn't * + * render if the GPU name contains "Radeon". */ { R"(\\WOFF\.exe$)", {{ { "d3d11.disableMsaa", "True" }, + { "dxgi.customDeviceDesc", "DXVK Device" }, }} }, - /* Mary Skelter 2 - Broken MSAA */ + /* Mary Skelter 2 - Broken MSAA */ { R"(\\MarySkelter2\.exe$)", {{ { "d3d11.disableMsaa", "True" }, }} }, @@ -293,7 +311,6 @@ namespace dxvk { /* God of War - relies on NVAPI/AMDAGS for * * barrier stuff, needs nvapi for DLSS */ { R"(\\GoW\.exe$)", {{ - { "d3d11.ignoreGraphicsBarriers", "True" }, { "d3d11.relaxedBarriers", "True" }, { "dxgi.hideNvidiaGpu", "False" }, { "dxgi.maxFrameLatency", "1" }, @@ -352,6 +369,11 @@ namespace dxvk { { R"(\\MSFC\.exe$)", {{ { "dxgi.maxFrameRate", "60" }, }} }, + /* The Evil Within 2 * + * Game speeds up above 120 fps */ + { R"(\\TEW2\.exe$)", {{ + { "dxgi.maxFrameRate", "120" }, + }} }, /* Cardfight!! Vanguard Dear Days: * * Submits command lists multiple times */ { R"(\\VG2\.exe$)", {{ @@ -443,11 +465,16 @@ namespace dxvk { { "d3d9.maxFrameRate", "-1" }, { "dxgi.maxFrameRate", "-1" }, }} }, - /* Kena: Bridge of Spirits: intel water * + /* Kena: Bridge of Spirits: intel water * * flickering issues */ { R"(\\Kena-Win64-Shipping\.exe$)", {{ { "dxgi.hideIntelGpu", "True" }, }} }, + /* Earth Defense Force 5 */ + { R"(\\EDF5\.exe$)", {{ + { "dxgi.tearFree", "False" }, + { "dxgi.syncInterval", "1" }, + }} }, /* The Hurricane of the Varstray * * Too fast above 60fps */ { R"(\\Varstray_steam(_demo)?\.exe$)", {{ @@ -458,11 +485,6 @@ namespace dxvk { { R"(\\Styx2-Win64-Shipping\.exe$)", {{ { "d3d11.constantBufferRangeCheck", "True" }, }} }, - /* Far Cry 5 and New Dawn * - * Invisible terrain on Intel */ - { R"(\\FarCry(5|NewDawn)\.exe$)", {{ - { "d3d11.zeroInitWorkgroupMemory", "True" }, - }} }, /* Romancing Saga 3 * * Render issues on D3D11 * * Lets try with just constantBufferRangeCheck */ @@ -474,6 +496,14 @@ namespace dxvk { { R"(\\Wargame European Escalation\\WarGame\.exe$)", {{ { "dxgi.hideNvidiaGpu", "True" }, }} }, + /* Guilty Gear - Speeds up above 60 fps */ + { R"(\\GuiltyGear\.exe$)", {{ + { "dxgi.maxFrameRate", "60" }, + }} }, + /* Everybody's Gone to the Rapture - CPU perf */ + { R"(\\Rapture_Release\.exe$)", {{ + { "d3d11.cachedDynamicResources", "a" }, + }} }, /* VLADiK BRUTAL * * totally broken with NVAPI */ { R"(\\VLADiK_BRUTAL-Win64-Shipping\.exe$)", {{ @@ -541,7 +571,7 @@ namespace dxvk { { "d3d9.cachedDynamicBuffers", "True" }, }} }, /* Dead Space uses the a NULL render target instead - of a 1x1 one if DF24 is NOT supported + of a 1x1 one if DF24 is NOT supported Mouse and physics issues above 60 FPS Built-in Vsync Locks the game to 30 FPS */ { R"(\\Dead Space\.exe$)", {{ @@ -600,7 +630,7 @@ namespace dxvk { { R"(\\TESV\.exe$)", {{ { "d3d9.customVendorId", "1002" }, }} }, - /* RTHDRIBL Demo + /* RTHDRIBL Demo Uses DONOTWAIT after GetRenderTargetData then goes into an infinite loop if it gets D3DERR_WASSTILLDRAWING. @@ -668,7 +698,8 @@ namespace dxvk { { "d3d9.customVendorId", "10de" }, { "d3d9.customDeviceId", "0402" }, }} }, - /* Warhammer: Online */ + /* Warhammer: Online * + * Overly bright ground textures on Nvidia */ { R"(\\(WAR(-64)?|WARTEST(-64)?)\.exe$)", {{ { "d3d9.customVendorId", "1002" }, }} }, @@ -696,15 +727,10 @@ namespace dxvk { { R"(\\Demonstone\.exe$)", {{ { "d3d9.maxFrameRate", "60" }, }} }, - /* Far Cry 1 has worse water rendering when it detects AMD GPUs */ + /* Far Cry 1 has worse water rendering on AMD GPUs */ { R"(\\FarCry\.exe$)", {{ { "d3d9.customVendorId", "10de" }, }} }, - /* Earth Defense Force 5 */ - { R"(\\EDF5\.exe$)", {{ - { "dxgi.tearFree", "False" }, - { "dxgi.syncInterval", "1" }, - }} }, /* Sine Mora EX */ { R"(\\SineMoraEX\.exe$)", {{ { "d3d9.maxFrameRate", "60" }, @@ -784,12 +810,13 @@ namespace dxvk { { "d3d9.memoryTrackTest", "True" }, { "d3d9.maxAvailableMemory", "2048" }, }} }, - /* Myst V End of Ages + /* Myst V End of Ages Game has white textures on amd radv. Expects Nvidia, Intel or ATI VendorId. "Radeon" in gpu description also works */ { R"(\\eoa\.exe$)", {{ { "d3d9.customVendorId", "10de" }, + { "d3d9.cachedDynamicBuffers", "True" }, }} }, /* Beyond Good And Evil * * Fixes missing sun and light shafts * @@ -806,7 +833,7 @@ namespace dxvk { { R"(\\swtor\.exe$)", {{ { "d3d9.forceSamplerTypeSpecConstants", "True" }, }} }, - /* Bionic Commando + /* Bionic Commando Physics break at high fps */ { R"(\\bionic_commando\.exe$)", {{ { "d3d9.maxFrameRate", "60" }, @@ -831,6 +858,13 @@ namespace dxvk { { R"(\\yso_win\.exe$)", {{ { "d3d9.maxFrameLatency", "1" }, }} }, + /* The Witcher (2007) - Very long loading * + * times and inventory hair explosion at * + * very high fps */ + { R"(\\witcher\.exe$)", {{ + { "d3d9.cachedDynamicBuffers", "True" }, + { "d3d9.maxFrameRate", "300" }, + }} }, /* Heroes of Annihilated Empires * * Has issues with texture rendering and * * video memory detection otherwise. */ @@ -846,10 +880,6 @@ namespace dxvk { { R"(\\SinEpisodes\.exe$)", {{ { "d3d9.memoryTrackTest", "True" }, }} }, - /* Witcher 1: Very long loading times */ - { R"(\\witcher\.exe$)", {{ - { "d3d9.cachedDynamicBuffers", "True" }, - }} }, /* Hammer World Editor */ { R"(\\(hammer(plusplus)?|mallet|wc)\.exe$)", {{ { "d3d9.cachedDynamicBuffers", "True" }, @@ -977,18 +1007,21 @@ namespace dxvk { { "d3d9.cachedDynamicBuffers", "True" }, }} }, /* Prototype * - * Incorrect shadows on AMD & Intel */ - { R"(\\prototypef\.exe$)", {{ - { "d3d9.supportDFFormats", "False" }, + * Incorrect shadows on AMD & Intel. * + * AA 4x can not be selected above 2GB vram */ + { R"(\\prototypef\.exe$)", {{ + { "d3d9.customVendorId", "10de" }, + { "dxgi.maxDeviceMemory", "2047" }, }} }, /* Fallout New Vegas - Various visual issues * * with mods such as New Vegas Reloaded */ - { R"(\\FalloutNV\.exe$)", {{ + { R"(\\FalloutNV(Launcher)?\.exe$)", {{ { "d3d9.floatEmulation", "Strict" }, + { "d3d9.customVendorId", "1002" }, }} }, /* Dungeons and Dragons: Dragonshard * * Massive FPS decreases in some scenes */ - { R"(\\Dragonshard\.exe$)", {{ + { R"(\\Dragonshard\.exe$)", {{ { "d3d9.cachedDynamicBuffers", "True" }, }} }, /* Battle for Middle-earth 2 and expansion * @@ -1032,12 +1065,10 @@ namespace dxvk { { R"(\\SecretWorldLegends\.exe$)", {{ { "d3d9.memoryTrackTest", "True" }, }} }, - /* Far Cry 2: Set vendor ID to Nvidia to * - * avoid vegetation artifacts on Intel, and * - * set apitrace mode to True to improve perf * - * on all hardware. */ + /* Far Cry 2: * + * Set cached dynamic buffers to True to * + * improve perf on all hardware. */ { R"(\\(FarCry2|farcry2game)\.exe$)", {{ - { "d3d9.customVendorId", "10de" }, { "d3d9.cachedDynamicBuffers", "True" }, }} }, /* Alpha Protocol - Rids unwanted reflections */ @@ -1055,11 +1086,9 @@ namespace dxvk { { "d3d9.floatEmulation", "Strict" }, }} }, /* Star Wars Empire at War & expansion * - * On Intel the Water & Shader Detail option * - * can't be modified. In base game the AA * - * option dissapears at 2075MB vram and above */ + * In base game the AA option dissapears at * + * 2075MB vram and above */ { R"(\\(StarWarsG|sweaw|swfoc)\.exe$)", {{ - { "d3d9.customVendorId", "1002" }, { "d3d9.maxAvailableMemory", "2048" }, { "d3d9.memoryTrackTest", "True" }, }} }, @@ -1082,7 +1111,7 @@ namespace dxvk { { R"(\\THUMPER_dx9\.exe$)", {{ { "d3d9.floatEmulation", "Strict" }, }} }, - /* Pirate Huner - Prevents crash */ + /* Pirate Hunter - Prevents crash */ { R"(\\PH\.exe$)", {{ { "d3d9.memoryTrackTest", "True" }, { "d3d9.maxAvailableMemory", "2048" }, @@ -1177,9 +1206,10 @@ namespace dxvk { { R"(\\NFSHP2\.exe$)", {{ { "d3d9.cachedDynamicBuffers", "True" }, }} }, - /* Project I.G.I. 2: Covert Strike */ + /* Project I.G.I. 2: Covert Strike * + * Very stuttery frametime with own framecap */ { R"(\\igi2\.exe$)", {{ - { "d3d9.cachedDynamicBuffers", "True" }, + { "d3d9.maxFrameRate", "60" }, }} }, /* Treasure Planet: Battle at Procyon * * Declares v5 as color but shader uses v6 */ @@ -1309,15 +1339,626 @@ namespace dxvk { * due to queue syncs on certain race tracks */ { R"(\\Smash up Derby\\cars\.exe$)", {{ { "d3d9.allowDirectBufferMapping", "False" }, + + /**********************************************/ + /* D3D7 GAMES */ + /**********************************************/ + + /* 1NSANE - Invalid buffer discards and * + * artifacting when using a T&L HAL device, * + * and broken main menu animations. */ + { R"(\\(1|I)nsane\\Game\.exe$)", {{ + { "d3d9.maxFrameRate", "-240" }, + { "ddraw.forceSWVP", "True" }, + }} }, + /* Arx Fatalis */ + { R"(\\arx\.exe$)", {{ + { "ddraw.emulateFSAA", "True" }, + }} }, + /* Sacrifice - Prevents hitching on asset * + * loading and fixes broken AI above 60 FPS. * + * Also support 32-bit modes, which need D32. */ + { R"(\\Sacrifice\.exe$)", {{ + { "d3d9.cachedWriteOnlyBuffers", "True" }, + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.emulateFSAA", "True" }, + { "ddraw.useD24X8forD32", "True" }, + }} }, + /* Battle Isle: The Andosia War - Performance * + * and black screen prevention on startup, * + * also capped to prevent scroll speed issues */ + { R"(\\bitaw\.exe$)", {{ + { "d3d9.cachedWriteOnlyBuffers", "True" }, + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.backBufferGuard", "Strict" }, + }} }, + /* Startopia */ + { R"(\\startopia\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Escape from Monkey Island * + * Fixes broken physics, and flip logic */ + { R"(\\Monkey4\.exe$)", {{ + { "d3d9.maxFrameRate", "-30" }, + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Gothic 1 - broken physics and * + * flickering on the loading screen */ + { R"(\\GOTHIC(Mod)?\.EXE$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Gothic 2 / Night of the Raven * + * Broken physics and sliding speed, and * + * flickering on the loading screen */ + { R"(\\Gothic2\.exe)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Blade of Darkness - broken physics, main * + * menu transitions, animations and GUI */ + { R"(\\Blade\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Hogs of War - Fixes animation speed */ + { R"(\\warhogs_\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + }} }, + /* Parkan: Iron Strategy - Performance */ + { R"(\\iron_3d\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "d3d9.cachedWriteOnlyBuffers", "True" }, + }} }, + /* Dungeon Siege */ + { R"(\\DungeonSiege\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + { "ddraw.backBufferWriteBack", "True" }, + }} }, + /* Empire Earth / Art of Conquest */ + { R"(\\(Empire Earth|EE-AOC)\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Etherlords * + * Needs R3G3B2 support for text rendering */ + { R"(\\Etherlords\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + { "ddraw.supportR3G3B2", "True" }, + }} }, + /* Etherlords 2 * + * Needs R3G3B2 support for text rendering */ + { R"(\\Etherlords2\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + { "ddraw.supportR3G3B2", "True" }, + }} }, + /* Evil Islands */ + { R"(\\Evil Islands\\game\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Star Trek: Armada */ + { R"(\\Armada\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* SCP - Containment Breach * + * Crashes without multithreading protection */ + { R"(\\SCP - Containment Breach\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + { "ddraw.emulateFSAA", "True" }, + { "ddraw.forceMultiThreaded", "True" }, + }} }, + /* Unreal * + * Fixes missing mip map uploads and physics */ + { R"(\\Unreal\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Unreal Tournament * + * Fixes missing mip map uploads */ + { R"(\\UnrealTournament\.exe$)", {{ + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Rune * + * Fixes missing mip map uploads and physics */ + { R"(\\Rune\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Deus Ex * + * Fixes missing mip map uploads and physics */ + { R"(\\DeusEx\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Clive Barker's Undying * + * Fixes missing mip map uploads, and broken * + * cutscene playback at high frame rates */ + { R"(\\Undying\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* X-COM: Enforcer * + * Fixes missing mip map uploads and physics */ + { R"(\\XCom\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* The Wheel of Time * + * Fixes missing mip map uploads and physics */ + { R"(\\WoT\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Harry Potter and the Chamber of Secrets * + * Fixes missing mip map uploads and physics */ + { R"(\\Harry Potter.*\\system\\Game\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Harry Potter and the Philosopher's Stone * + * Fixes missing mip map uploads and physics */ + { R"(\\HP\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Messiah - Fixes missing mip map uploads * + * and cutscene playback / physics */ + { R"(\\MessiahD3D\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.emulateFSAA", "True" }, + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Might and Magic IX / No One Lives Forever */ + { R"(\\lithtech\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + }} }, + /* 3DMark2000 - Performance */ + { R"(\\3DMark2000\.exe$)", {{ + { "d3d9.cachedWriteOnlyBuffers", "True" }, + }} }, + /* Carmageddon TDR 2000 - Main menu speed */ + { R"(\\TDR2000\.exe$)", {{ + { "d3d9.maxFrameRate", "-120" }, + }} }, + /* Disciples II - Excessive map scroll speed */ + { R"(\\Discipl2\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + }} }, + /* Hitman: Codename 47 - Broken physics and * + * loading screens / menu transitions */ + { R"(\\hitman\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.emulateFSAA", "True" }, + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Screamer 4x4 - Broken menu animation speed */ + { R"(\\Screamer4x4_d3d\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + }} }, + /* (The) Summoner - Accelerated game speed * + * and fix for nonsensical viewport values */ + { R"(\\Sum\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.emulateFSAA", "True" }, + }} }, + /* Wizardry 8 - Fixes broken input handling */ + { R"(\\Wiz8\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + }} }, + /* Giants: Citizen Kabuto * + * Broken input handling at high framerates */ + { R"(\\Giants\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + }} }, + /* The Mystery of the Druids */ + { R"(\\edd\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Silent Hunter II - Broken input handling */ + { R"(\\Silent Hunter.*\\(Sim|Shell(1)?)\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + }} }, + /* Enemy Engaged: Comanche vs Hokum */ + { R"(\\cohokum\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* The Nations (Gold Edition) */ + { R"(\\The Nations.*\\bin\\game\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Need for Speed: Porsche Unleashed * + * Fixes missing mip maps on car models */ + { R"(\\(Porsche|nfs5)\.exe$)", {{ + { "ddraw.autoGenMipMaps", "True" }, + { "ddraw.backBufferWriteBack", "True" }, + { "ddraw.backBufferGuard", "Disabled" }, + }} }, + /* Soulbringer - Uses legacy ddraw interfaces * + * and has broken rendering with direct * + * buffer mapping on T&L devices */ + { R"(\\SoulbringeVC(noeax)?\.exe$)", {{ + { "d3d9.allowDirectBufferMapping", "False" }, + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Star Trek: Deep Space Nine - The Fallen * + * Fixes missing mip map uploads */ + { R"(\\DS9\.exe$)", {{ + { "ddraw.autoGenMipMaps", "True" }, + }} }, + /* Sacred - Fixes transition artifacting */ + { R"(\\Sacred\.exe$)", {{ + { "ddraw.emulateFSAA", "True" }, + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* StarLancer */ + { R"(\\Lancer\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* The Settlers IV */ + { R"(\\S4_Main\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Spider-Man (2001) - broken cutscenes */ + { R"(\\SpideyPC\.exe$)", {{ + { "d3d9.maxFrameRate", "30" }, + }} }, + /* Wizards & Warriors */ + { R"(\\deep6\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Age of Wonders: Shadow Magic */ + { R"(\\AoWSM(Compat)?\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Age of Wonders II: The Wizard's Throne */ + { R"(\\AoW2\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Hard Truck 2: King of the Road */ + { R"(\\king\.exe$)", {{ + { "ddraw.colorKeyCompatibility", "True" }, + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Anno 1503 */ + { R"(\\1503Startup\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Knight Rider: The Game * + * Fixes in-game vehicle environment maps * + * and Z-fighting artifacts when using D16 */ + { R"(\\(Knight Rider|KR( Demo)?)\.exe$)", {{ + { "ddraw.supportD16", "False" }, + { "ddraw.forceSingleBackBuffer", "True" }, + { "ddraw.backBufferWriteBack", "True" }, + }} }, + /* Knight Rider: The Game 2 * + * Fixes in-game vehicle environment maps */ + { R"(\\KR2\.exe$)", {{ + { "ddraw.backBufferWriteBack", "True" }, + }} }, + /* Real Myst * + * Fixes menu and save game backgrounds */ + { R"(\\RealMYST\.exe$)", {{ + { "ddraw.backBufferWriteBack", "True" }, + }} }, + /* Total Club Manager 2003 * + * Fixes in-game blur transition effects */ + { R"(\\TCM2003\.exe$)", {{ + { "ddraw.backBufferWriteBack", "True" }, + }} }, + /* Sim City 4 * + * Fixes broken overlays and 3D elements */ + { R"(\\SimCity 4\.exe$)", {{ + { "ddraw.depthWriteBack", "True" }, + { "ddraw.backBufferWriteBack", "True" }, + }} }, + /* Radeon's Ark (ATI Radeon 7000 Tech Demo) * + * Needs custom vendor ID to run on anything * + * outside AMD, and a frame cap to not freeze * + * or slow down upwards of 500 FPS. Legacy * + * DISCARD handling fixes missing geometry. */ + { R"(\\Radeon'sArk1.3\.exe$)", {{ + { "d3d9.customVendorId", "1002" }, + { "d3d9.maxFrameRate", "-500" }, + { "ddraw.forceLegacyDiscard", "True" }, + }} }, + /* Tribes 2 - fixes rendering and performance */ + { R"(\\Tribes2\.exe$)", {{ + { "ddraw.ignoreExclusiveMode", "True" }, + { "ddraw.forceSWVP", "True" }, + }} }, + /* Space Empires V */ + { R"(\\SE5\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Will Rock * + * Fixes missing save game screenshots */ + { R"(\\WillRock\.exe$)", {{ + { "ddraw.emulateFSAA", "True" }, + { "ddraw.backBufferWriteBack", "True" }, + }} }, + + /**********************************************/ + /* D3D6 GAMES */ + /**********************************************/ + + /* Drakan: Order of the Flame * + * Fixes physics glitches at over 60 FPS and * + * missing pause / save game backgrounds. We * + * also prevent depth stencil uploads to fix * + * performance loss when lens flares are * + * enabled, because that causes depth stencil * + * locks for each dynamic light source */ + { R"(\\Drakan\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.backBufferWriteBack", "True" }, + { "ddraw.uploadDepthStencils", "False" }, + }} }, + /* O.R.B: Off-World Resource Base * + * Uses windowed present mode in full-screen */ + { R"(\\orb\.exe$)", {{ + { "ddraw.ignoreExclusiveMode", "True" }, + }} }, + /* Might and Magic VII: For Blood and Honor */ + { R"(\\MM7(-Rel)?\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Might and Magic VIII: Day of the Destroyer */ + { R"(\\MM8(-Rel)?\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Omikron: The Nomad Soul * + * Lights and other effects break over 30 FPS.* + * The pause menu and dialogue subtitles are * + * missing without proxy presentation. */ + { R"(\\Omikron.*\\Runtime\.exe$)", {{ + { "d3d9.maxFrameRate", "30" }, + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Urban Chaos * + * Uses windowed present mode in full-screen * + * and mixes up VP MinZ / MaxZ values. */ + { R"(\\fallen\.exe$)", {{ + { "ddraw.ignoreExclusiveMode", "True" }, + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Redline - Fixes missing weapon mip maps */ + { R"(\\Redline\.exe$)", {{ + { "ddraw.autoGenMipMaps", "True" }, + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* 3DMark 99 (Max) - Enables VSync by default * + * (probably due to hardware and/or driver * + * limitations of the time), and needs mixed * + * SWVP for performance reasons */ + { R"(\\3dmark\.exe$)", {{ + { "d3d9.presentInterval", "0" }, + { "d3d9.allowDirectBufferMapping", "False" }, + }} }, + /* Hidden & Dangerous (: Action Pack) * + * Prevents crashing on startup */ + { R"(\\h&d\.exe$)", {{ + { "d3d9.allowDirectBufferMapping", "False" }, + }} }, + /* Dungeon Keeper 2 */ + { R"(\\DKII(-DX)?\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Star Wars: Rogue Squadron 3D */ + { R"(\\Rogue Squadron\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + }} }, + /* Blood II: The Chosen */ + { R"(\\Blood.*\\Client\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Shogo: Mobile Armor Division */ + { R"(\\Shogo.*\\Client\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* KISS: Psycho Circus - The Nightmare Child */ + { R"(\\(KISS.*|Psycho.*)\\client\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Enemy Engaged: Apache vs Havoc */ + { R"(\\aphavoc\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Star Trek: Starfleet Command */ + { R"(\\Starfleet\.exe$)", {{ + { "ddraw.forceMultiThreaded", "True" }, + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Expendable */ + { R"(\\Expendable\\go_start\.exe$)", {{ + { "ddraw.emulateFSAA", "True" }, + }} }, + /* F/A-18E Super Hornet */ + { R"(\\F18\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Total Annihilation: Kingdoms */ + { R"(\\KINGDOMS\.icd$)", {{ + { "ddraw.emulateFSAA", "True" }, + { "ddraw.forcePOW2Textures", "True" }, + }} }, + /* Star Wars Episode I: Racer */ + { R"(\\SWEP1RCR\.exe$)", {{ + { "ddraw.depthWriteBack", "True" }, + }} }, + /* Gorky 17 - Fixes crash on game start */ + { R"(\\gorky17\.exe$)", {{ + { "ddraw.depthWriteBack", "True" }, + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Revenant */ + { R"(\\Revenant\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Re-Volt */ + { R"(\\revolt\.exe$)", {{ + { "ddraw.emulateFSAA", "True" }, + }} }, + /* Sea Dogs */ + { R"(\\Sea Dogs\\ENGINE\.exe$)", {{ + { "ddraw.emulateFSAA", "True" }, + }} }, + /* Empire of the Ants */ + { R"(\\Empire of the Ants\\Game\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Slave Zero - will not start in 32-bit * + * color mode without D32 support */ + { R"(\\SlaveZero\.exe$)", {{ + { "ddraw.useD24X8forD32", "True" }, + }} }, + /* Nocturne */ + { R"(\\nocturne\.exe$)", {{ + { "ddraw.depthWriteBack", "True" }, + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Arabian Nights * + * Fixes flickering during level load */ + { R"(\\Arabian Nights\\_start\.exe$)", {{ + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Metal Fatigue * + * Fixes unit and building transparency */ + { R"(\\MFatigue\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + { "ddraw.colorKeyCompatibility", "True" }, + }} }, + /* Simon The Sorcerer 3D * + * Fixes Z-fighting artifacts with D16 */ + { R"(\\Simon3D\.exe$)", {{ + { "ddraw.supportD16", "False" }, + }} }, + /* Crusaders of Might and Magic */ + { R"(\\crusaders\.exe$)", {{ + { "ddraw.backBufferWriteBack", "True" }, + { "ddraw.backBufferGuard", "Disabled" }, + }} }, + /* DethKarz - fixes crash post intro playback */ + { R"(\\Dethkarz\.exe$)", {{ + { "ddraw.mask8BitModes", "True" }, + { "ddraw.colorKeyCompatibility", "True" }, + }} }, + /* Tomb Raider Chronicles */ + { R"(\\PCTomb5\.exe$)", {{ + { "ddraw.backBufferWriteBack", "True" }, + }} }, + + /**********************************************/ + /* D3D5 GAMES */ + /**********************************************/ + + /* Descent: FreeSpace - The Great War */ + { R"(\\FS\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Populous: The Beginning */ + { R"(\\D3DPopTB(UW)?\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* N.I.C.E 2 - Fixes main menu flickering */ + { R"(\\n2_(std|arc)\.exe$)", {{ + { "d3d9.maxFrameRate", "-60" }, + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Twisted Metal 2 */ + { R"(\\tm2\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Mobil 1 Rally Championship * + * Crashes on certain tracks above 30 FPS */ + { R"(\\Ral\.exe$)", {{ + { "d3d9.maxFrameRate", "30" }, + }} }, + /* Nightmare Creatures * + * Fixes presentation and physics, which is * + * tied to framerate in various situations */ + { R"(\\NC(_V12)?\.exe$)", {{ + { "d3d9.maxFrameRate", "-30" }, + { "ddraw.ignoreExclusiveMode", "True" }, + }} }, + /* Deathtrap Dungeon * + * Accelerated menu animations above 30 FPS */ + { R"(\\DD_CD\.exe$)", {{ + { "d3d9.maxFrameRate", "-30" }, + }} }, + /* FIFA '99 */ + { R"(\\fifa99\.exe$)", {{ + { "ddraw.emulateFSAA", "True" }, + }} }, + /* The Longest Journey */ + { R"(\\The Longest Journey\\game\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Wing Commander: Prophecy */ + { R"(\\prophecy\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Tom Clancy's Rainbow Six * + * Fixes broken color key transparency */ + { R"(\\RainbowSix\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + { "ddraw.colorKeyCompatibility", "True" }, + }} }, + /* Incoming - fixes load screen flickering */ + { R"(\\incoming\.exe$)", {{ + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Lands of Lore III */ + { R"(\\LOL3\.dat$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Virtua Fighter 2 */ + { R"(\\VF2\.exe$)", {{ + { "ddraw.backBufferWriteBack", "True" }, + { "ddraw.backBufferGuard", "Disabled" }, + }} }, + /* Return to Krondor */ + { R"(\\RtK\.exe$)", {{ + { "ddraw.backBufferWriteBack", "True" }, + { "ddraw.backBufferGuard", "Disabled" }, + }} }, + /* RoBoRumble */ + { R"(\\rr_dx5\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + + /**********************************************/ + /* D3D3 GAMES */ + /**********************************************/ + + /* Outlaws - fixes pause menu backgrounds */ + { R"(\\olwin\.exe$)", {{ + { "ddraw.backBufferWriteBack", "True" }, }} }, - }}; + /* Star Wars: Jedi Knight: Dark Forces II */ + { R"(\\JK\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Star Wars: Jedi Knight: Mysteries of the Sith */ + { R"(\\JKM\.exe$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + /* Moto Racer 2 - fixes menu flickering */ + { R"(\\moto\.exe$)", {{ + { "ddraw.forceSingleBackBuffer", "True" }, + }} }, + /* Monster Truck Madness */ + { R"(\\MONSTER\.EXE$)", {{ + { "ddraw.forceProxiedPresent", "True" }, + }} }, + + }; static bool isWhitespace(char ch) { return ch == ' ' || ch == '\x9' || ch == '\r'; } - + static bool isValidKeyChar(char ch) { return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') @@ -1354,12 +1995,12 @@ namespace dxvk { while (n < e) key << line[n++]; - + ctx.active = key.str() == env::getExeName(); } else { while (n < line.size() && isValidKeyChar(line[n])) key << line[n++]; - + // Check whether the next char is a '=' n = skipWhitespace(line, n); if (n >= line.size() || line[n] != '=') @@ -1379,7 +2020,7 @@ namespace dxvk { } else value << line[n++]; } - + if (ctx.active) config.setOption(key.str(), value.str()); } @@ -1439,7 +2080,7 @@ namespace dxvk { int32_t& result) { if (value.size() == 0) return false; - + // Parse sign, don't allow '+' int32_t sign = 1; size_t start = 0; @@ -1455,7 +2096,7 @@ namespace dxvk { for (size_t i = start; i < value.size(); i++) { if (value[i] < '0' || value[i] > '9') return false; - + intval *= 10; intval += value[i] - '0'; } @@ -1464,8 +2105,8 @@ namespace dxvk { result = sign * intval; return true; } - - + + bool Config::parseOptionValue( const std::string& value, Tristate& result) { @@ -1505,7 +2146,7 @@ namespace dxvk { std::regex expr(pair.first, std::regex::extended | std::regex::icase); return std::regex_search(appName, expr); }); - + if (appConfig != g_appDefaults.end()) { // Inform the user that we loaded a default config Logger::info(str::format("Found built-in config:")); @@ -1529,7 +2170,7 @@ namespace dxvk { if (filePath == "") filePath = "dxvk.conf"; - + // Open the file if it exists std::ifstream stream(str::tows(filePath.c_str()).c_str()); @@ -1562,7 +2203,7 @@ namespace dxvk { for(auto l : str::split(confLine, ";")) parseUserConfigLine(config, ctx, std::string(l.data(), l.size())); } - + return config; } From 3721e8fa1cad45b5630b992a28ab7ce4b82ae16a Mon Sep 17 00:00:00 2001 From: pythonlover02 Date: Wed, 8 Apr 2026 06:53:41 -0300 Subject: [PATCH 2/4] [util] fix compilation error --- src/util/config/config.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp index eb8261e7830..2e12b3cd089 100644 --- a/src/util/config/config.cpp +++ b/src/util/config/config.cpp @@ -1339,6 +1339,7 @@ namespace dxvk { * due to queue syncs on certain race tracks */ { R"(\\Smash up Derby\\cars\.exe$)", {{ { "d3d9.allowDirectBufferMapping", "False" }, + }} }, /**********************************************/ /* D3D7 GAMES */ From aab60bd4c7030d5bb53dffb74e11435897bd2bc0 Mon Sep 17 00:00:00 2001 From: pythonlover02 Date: Wed, 8 Apr 2026 06:59:48 -0300 Subject: [PATCH 3/4] [util] another comp error --- src/util/config/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp index 2e12b3cd089..a31a5595291 100644 --- a/src/util/config/config.cpp +++ b/src/util/config/config.cpp @@ -1952,7 +1952,7 @@ namespace dxvk { { "ddraw.forceProxiedPresent", "True" }, }} }, - }; + }}; static bool isWhitespace(char ch) { From 056acef2139d016ddf51b2135aeff150c2949ce3 Mon Sep 17 00:00:00 2001 From: pythonlover02 Date: Wed, 8 Apr 2026 22:56:41 -0300 Subject: [PATCH 4/4] [d3d9] remove the FF shaders --- .../shaders/d3d9_fixed_function_common.glsl | 365 --------- .../shaders/d3d9_fixed_function_frag.frag | 4 - .../shaders/d3d9_fixed_function_frag.glsl | 722 ----------------- .../d3d9_fixed_function_frag_sample.frag | 6 - .../shaders/d3d9_fixed_function_vert.vert | 725 ------------------ 5 files changed, 1822 deletions(-) delete mode 100644 src/d3d9/shaders/d3d9_fixed_function_common.glsl delete mode 100644 src/d3d9/shaders/d3d9_fixed_function_frag.frag delete mode 100644 src/d3d9/shaders/d3d9_fixed_function_frag.glsl delete mode 100644 src/d3d9/shaders/d3d9_fixed_function_frag_sample.frag delete mode 100644 src/d3d9/shaders/d3d9_fixed_function_vert.vert diff --git a/src/d3d9/shaders/d3d9_fixed_function_common.glsl b/src/d3d9/shaders/d3d9_fixed_function_common.glsl deleted file mode 100644 index 2ac989849be..00000000000 --- a/src/d3d9/shaders/d3d9_fixed_function_common.glsl +++ /dev/null @@ -1,365 +0,0 @@ -const float FloatMaxValue = 340282346638528859811704183484516925440.0; - -const uint TextureStageCount = 8; - -#define D3DFOGMODE uint -const uint D3DFOG_NONE = 0; -const uint D3DFOG_EXP = 1; -const uint D3DFOG_EXP2 = 2; -const uint D3DFOG_LINEAR = 3; - -struct D3D9RenderStateInfo { - float fogColor[3]; - float fogScale; - float fogEnd; - float fogDensity; - - uint alphaRef; - - float pointSize; - float pointSizeMin; - float pointSizeMax; - float pointScaleA; - float pointScaleB; - float pointScaleC; -}; - - -// Thanks SPIRV-Cross -spirv_instruction(set = "GLSL.std.450", id = 79) float spvNMin(float, float); -spirv_instruction(set = "GLSL.std.450", id = 79) vec2 spvNMin(vec2, vec2); -spirv_instruction(set = "GLSL.std.450", id = 79) vec3 spvNMin(vec3, vec3); -spirv_instruction(set = "GLSL.std.450", id = 79) vec4 spvNMin(vec4, vec4); -spirv_instruction(set = "GLSL.std.450", id = 81) float spvNClamp(float, float, float); -spirv_instruction(set = "GLSL.std.450", id = 81) vec2 spvNClamp(vec2, vec2, vec2); -spirv_instruction(set = "GLSL.std.450", id = 81) vec3 spvNClamp(vec3, vec3, vec3); -spirv_instruction(set = "GLSL.std.450", id = 81) vec4 spvNClamp(vec4, vec4, vec4); - - -// Dynamic "spec constants" -// Binding has to match with getSpecConstantBufferSlot in dxso_util.h -layout(set = 0, binding = 31, scalar) uniform SpecConsts { - uint dynamicSpecConstDword[20]; -}; - -layout (constant_id = 0) const uint SpecConstDword0 = 0; -layout (constant_id = 1) const uint SpecConstDword1 = 0; -layout (constant_id = 2) const uint SpecConstDword2 = 0; -layout (constant_id = 3) const uint SpecConstDword3 = 0; -layout (constant_id = 4) const uint SpecConstDword4 = 0; -layout (constant_id = 5) const uint SpecConstDword5 = 0; -layout (constant_id = 6) const uint SpecConstDword6 = 0; -layout (constant_id = 7) const uint SpecConstDword7 = 0; -layout (constant_id = 8) const uint SpecConstDword8 = 0; -layout (constant_id = 9) const uint SpecConstDword9 = 0; -layout (constant_id = 10) const uint SpecConstDword10 = 0; -layout (constant_id = 11) const uint SpecConstDword11 = 0; -layout (constant_id = 12) const uint SpecConstDword12 = 0; -layout (constant_id = 13) const uint SpecConstDword13 = 0; -layout (constant_id = 14) const uint SpecConstDword14 = 0; -layout (constant_id = 15) const uint SpecConstDword15 = 0; -layout (constant_id = 16) const uint SpecConstDword16 = 0; -layout (constant_id = 17) const uint SpecConstDword17 = 0; -layout (constant_id = 18) const uint SpecConstDword18 = 0; -layout (constant_id = 19) const uint SpecConstDword19 = 0; -layout (constant_id = 20) const uint SpecConstDword20 = 0; - -const uint SpecSamplerType = 0; -const uint SpecSamplerDepthMode = 1; -const uint SpecAlphaCompareOp = 2; -const uint SpecSamplerProjected = 3; -const uint SpecSamplerNull = 4; -const uint SpecAlphaPrecisionBits = 5; -const uint SpecFogEnabled = 6; -const uint SpecVertexFogMode = 7; -const uint SpecPixelFogMode = 8; -const uint SpecVertexShaderBools = 9; -const uint SpecPixelShaderBools = 10; -const uint SpecSamplerFetch4 = 11; -const uint SpecFFLastActiveTextureStage = 12; -const uint SpecSamplerDrefClamp = 13; -const uint SpecClipPlaneCount = 14; -const uint SpecPointMode = 15; -const uint SpecDrefScaling = 16; -const uint SpecFFGlobalSpecularEnabled = 17; -const uint SpecFFTextureStage0ColorOp = 18; -const uint SpecFFTextureStage0ColorArg1 = 19; -const uint SpecFFTextureStage0ColorArg2 = 20; -const uint SpecFFTextureStage0AlphaOp = 21; -const uint SpecFFTextureStage0AlphaArg1 = 22; -const uint SpecFFTextureStage0AlphaArg2 = 23; -const uint SpecFFTextureStage0ResultIsTemp = 24; -const uint SpecFFTextureStage1ColorOp = 25; -const uint SpecFFTextureStage1ColorArg1 = 26; -const uint SpecFFTextureStage1ColorArg2 = 27; -const uint SpecFFTextureStage1AlphaOp = 28; -const uint SpecFFTextureStage1AlphaArg1 = 29; -const uint SpecFFTextureStage1AlphaArg2 = 30; -const uint SpecFFTextureStage1ResultIsTemp = 31; -const uint SpecFFTextureStage2ColorOp = 32; -const uint SpecFFTextureStage2ColorArg1 = 33; -const uint SpecFFTextureStage2ColorArg2 = 34; -const uint SpecFFTextureStage2AlphaOp = 35; -const uint SpecFFTextureStage2AlphaArg1 = 36; -const uint SpecFFTextureStage2AlphaArg2 = 37; -const uint SpecFFTextureStage2ResultIsTemp = 38; -const uint SpecFFTextureStage3ColorOp = 39; -const uint SpecFFTextureStage3ColorArg1 = 40; -const uint SpecFFTextureStage3ColorArg2 = 41; -const uint SpecFFTextureStage3AlphaOp = 42; -const uint SpecFFTextureStage3AlphaArg1 = 43; -const uint SpecFFTextureStage3AlphaArg2 = 44; -const uint SpecFFTextureStage3ResultIsTemp = 45; -const uint SpecFFTextureStage4ColorOp = 46; -const uint SpecFFTextureStage4ColorArg1 = 47; -const uint SpecFFTextureStage4ColorArg2 = 48; -const uint SpecFFTextureStage4AlphaOp = 49; -const uint SpecFFTextureStage4AlphaArg1 = 50; -const uint SpecFFTextureStage4AlphaArg2 = 51; -const uint SpecFFTextureStage4ResultIsTemp = 52; -const uint SpecFFTextureStage5ColorOp = 53; -const uint SpecFFTextureStage5ColorArg1 = 54; -const uint SpecFFTextureStage5ColorArg2 = 55; -const uint SpecFFTextureStage5AlphaOp = 56; -const uint SpecFFTextureStage5AlphaArg1 = 57; -const uint SpecFFTextureStage5AlphaArg2 = 58; -const uint SpecFFTextureStage5ResultIsTemp = 59; -const uint SpecFFTextureStage6ColorOp = 60; -const uint SpecFFTextureStage6ColorArg1 = 61; -const uint SpecFFTextureStage6ColorArg2 = 62; -const uint SpecFFTextureStage6AlphaOp = 63; -const uint SpecFFTextureStage6AlphaArg1 = 64; -const uint SpecFFTextureStage6AlphaArg2 = 65; -const uint SpecFFTextureStage6ResultIsTemp = 66; -const uint SpecFFTextureStage7ColorOp = 67; -const uint SpecFFTextureStage7ColorArg1 = 68; -const uint SpecFFTextureStage7ColorArg2 = 69; -const uint SpecFFTextureStage7AlphaOp = 70; -const uint SpecFFTextureStage7AlphaArg1 = 71; -const uint SpecFFTextureStage7AlphaArg2 = 72; -const uint SpecFFTextureStage7ResultIsTemp = 73; -const uint SpecFFTextureStage0ColorArg0 = 74; -const uint SpecFFTextureStage1ColorArg0 = 75; -const uint SpecFFTextureStage2ColorArg0 = 76; -const uint SpecFFTextureStage3ColorArg0 = 77; -const uint SpecFFTextureStage4ColorArg0 = 78; -const uint SpecFFTextureStage5ColorArg0 = 79; -const uint SpecFFTextureStage6ColorArg0 = 80; -const uint SpecFFTextureStage7ColorArg0 = 81; -const uint SpecFFTextureStage0AlphaArg0 = 82; -const uint SpecFFTextureStage1AlphaArg0 = 83; -const uint SpecFFTextureStage2AlphaArg0 = 84; -const uint SpecFFTextureStage3AlphaArg0 = 85; -const uint SpecFFTextureStage4AlphaArg0 = 86; -const uint SpecFFTextureStage5AlphaArg0 = 87; -const uint SpecFFTextureStage6AlphaArg0 = 88; -const uint SpecFFTextureStage7AlphaArg0 = 89; -const uint SpecFFColorKeyEnabled = 90; -const uint SpecFFColorKeyCompatibility = 91; -const uint SpecFFUseLegacyLights = 92; -const uint SpecFFIsLegacyD3DLight2 = 93; -const uint SpecFFColorKeyLow = 94; -const uint SpecFFColorKeyHigh = 95; -const uint SpecConstantCount = 96; - -struct BitfieldPosition { - uint dwordOffset; - uint bitOffset; - uint sizeInBits; -}; - -// Needs to match d3d9_spec_constants.h -BitfieldPosition SpecConstLayout[SpecConstantCount] = { - { 0, 0, 32 }, // SamplerType - - { 1, 0, 21 }, // SamplerDepthMode - { 1, 21, 3 }, // AlphaCompareOp - { 1, 24, 8 }, // SamplerProjected - - { 2, 0, 21 }, // SamplerNull - { 2, 21, 4 }, // AlphaPrecisionBits - { 2, 25, 1 }, // FogEnabled - { 2, 26, 2 }, // VertexFogMode - { 2, 28, 2 }, // PixelFogMode - - { 3, 0, 16 }, // VertexShaderBools - { 3, 16, 16 }, // PixelShaderBools - - { 4, 0, 16 }, // SamplerFetch4 - { 4, 16, 3 }, // FFLastActiveTextureStage - - { 5, 0, 21 }, // SamplerDrefClamp - { 5, 21, 3 }, // ClipPlaneCount - { 5, 24, 2 }, // PointMode - { 5, 26, 5 }, // DrefScaling - - { 6, 31, 1 }, // FFGlobalSpecularEnabled. - - { 6, 0, 5 }, // FFTextureStage0ColorOp - { 6, 5, 5 }, // FFTextureStage0ColorArg1 - { 6, 10, 5 }, // FFTextureStage0ColorArg2 - { 6, 15, 5 }, // FFTextureStage0AlphaOp - { 6, 20, 5 }, // FFTextureStage0AlphaArg1 - { 6, 25, 5 }, // FFTextureStage0AlphaArg2 - { 6, 30, 1 }, // FFTextureStage0ResultIsTemp - - { 7, 0, 5 }, // FFTextureStage1ColorOp - { 7, 5, 5 }, // FFTextureStage1ColorArg1 - { 7, 10, 5 }, // FFTextureStage1ColorArg2 - { 7, 15, 5 }, // FFTextureStage1AlphaOp - { 7, 20, 5 }, // FFTextureStage1AlphaArg1 - { 7, 25, 5 }, // FFTextureStage1AlphaArg2 - { 7, 30, 1 }, // FFTextureStage1ResultIsTemp - - { 8, 0, 5 }, // FFTextureStage2ColorOp - { 8, 5, 5 }, // FFTextureStage2ColorArg1 - { 8, 10, 5 }, // FFTextureStage2ColorArg2 - { 8, 15, 5 }, // FFTextureStage2AlphaOp - { 8, 20, 5 }, // FFTextureStage2AlphaArg1 - { 8, 25, 5 }, // FFTextureStage2AlphaArg2 - { 8, 30, 1 }, // FFTextureStage2ResultIsTemp - - { 9, 0, 5 }, // FFTextureStage3ColorOp - { 9, 5, 5 }, // FFTextureStage3ColorArg1 - { 9, 10, 5 }, // FFTextureStage3ColorArg2 - { 9, 15, 5 }, // FFTextureStage3AlphaOp - { 9, 20, 5 }, // FFTextureStage3AlphaArg1 - { 9, 25, 5 }, // FFTextureStage3AlphaArg2 - { 9, 30, 1 }, // FFTextureStage3ResultIsTemp - - { 10, 0, 5 }, // FFTextureStage4ColorOp - { 10, 5, 5 }, // FFTextureStage4ColorArg1 - { 10, 10, 5 }, // FFTextureStage4ColorArg2 - { 10, 15, 5 }, // FFTextureStage4AlphaOp - { 10, 20, 5 }, // FFTextureStage4AlphaArg1 - { 10, 25, 5 }, // FFTextureStage4AlphaArg2 - { 10, 30, 1 }, // FFTextureStage4ResultIsTemp - - { 11, 0, 5 }, // FFTextureStage5ColorOp - { 11, 5, 5 }, // FFTextureStage5ColorArg1 - { 11, 10, 5 }, // FFTextureStage5ColorArg2 - { 11, 15, 5 }, // FFTextureStage5AlphaOp - { 11, 20, 5 }, // FFTextureStage5AlphaArg1 - { 11, 25, 5 }, // FFTextureStage5AlphaArg2 - { 11, 30, 1 }, // FFTextureStage5ResultIsTemp - - { 12, 0, 5 }, // FFTextureStage6ColorOp - { 12, 5, 5 }, // FFTextureStage6ColorArg1 - { 12, 10, 5 }, // FFTextureStage6ColorArg2 - { 12, 15, 5 }, // FFTextureStage6AlphaOp - { 12, 20, 5 }, // FFTextureStage6AlphaArg1 - { 12, 25, 5 }, // FFTextureStage6AlphaArg2 - { 12, 30, 1 }, // FFTextureStage6ResultIsTemp - - { 13, 0, 5 }, // FFTextureStage7ColorOp - { 13, 5, 5 }, // FFTextureStage7ColorArg1 - { 13, 10, 5 }, // FFTextureStage7ColorArg2 - { 13, 15, 5 }, // FFTextureStage7AlphaOp - { 13, 20, 5 }, // FFTextureStage7AlphaArg1 - { 13, 25, 5 }, // FFTextureStage7AlphaArg2 - { 13, 30, 1 }, // FFTextureStage7ResultIsTemp - - { 14, 0, 5 }, // FFTextureStage0ColorArg0 - { 14, 5, 5 }, // FFTextureStage1ColorArg0 - { 14, 10, 5 }, // FFTextureStage2ColorArg0 - { 14, 15, 5 }, // FFTextureStage3ColorArg0 - { 14, 20, 5 }, // FFTextureStage4ColorArg0 - { 14, 25, 5 }, // FFTextureStage5ColorArg0 - - { 15, 0, 5 }, // FFTextureStage6ColorArg0 - { 15, 5, 5 }, // FFTextureStage7ColorArg0 - { 15, 10, 5 }, // FFTextureStage0AlphaArg0 - { 15, 15, 5 }, // FFTextureStage1AlphaArg0 - { 15, 20, 5 }, // FFTextureStage2AlphaArg0 - { 15, 25, 5 }, // FFTextureStage3AlphaArg0 - - { 16, 0, 5 }, // FFTextureStage4AlphaArg0 - { 16, 5, 5 }, // FFTextureStage5AlphaArg0 - { 16, 10, 5 }, // FFTextureStage6AlphaArg0 - { 16, 15, 5 }, // FFTextureStage7AlphaArg0 - { 16, 20, 1 }, // FFColorKeyEnable - { 16, 21, 1 }, // FFColorKeyCompatibility - { 16, 22, 1 }, // FFUseLegacyLights - { 16, 23, 1 }, // SpecFFIsLegacyD3DLight2 - - { 17, 0, 24 }, // FFColorKeyLow - - { 18, 0, 24 }, // FFColorKeyHigh -}; - -bool specIsOptimized() { - return SpecConstDword20 != 0u; -} - -uint specDword(uint index) { - if (!specIsOptimized()) { - return dynamicSpecConstDword[index]; - } - - switch (index) { - case 0u: - return SpecConstDword0; - case 1u: - return SpecConstDword1; - case 2u: - return SpecConstDword2; - case 3u: - return SpecConstDword3; - case 4u: - return SpecConstDword4; - case 5u: - return SpecConstDword5; - case 6u: - return SpecConstDword6; - case 7u: - return SpecConstDword7; - case 8u: - return SpecConstDword8; - case 9u: - return SpecConstDword9; - case 10u: - return SpecConstDword10; - case 11u: - return SpecConstDword11; - case 12u: - return SpecConstDword12; - case 13u: - return SpecConstDword13; - case 14u: - return SpecConstDword14; - case 15u: - return SpecConstDword15; - case 16u: - return SpecConstDword16; - case 17u: - return SpecConstDword17; - case 18u: - return SpecConstDword18; - case 19u: - return SpecConstDword19; - case 20u: - return SpecConstDword20; - default: - return 0u; - } -} - -uint specUint(uint specConstIdx, uint bitOffset, uint bits) { - BitfieldPosition pos = SpecConstLayout[specConstIdx]; - uint dword = specDword(pos.dwordOffset); - return bitfieldExtract(dword, int(pos.bitOffset + bitOffset), int(bits)); -} - -uint specUint(uint specConstIdx) { - BitfieldPosition pos = SpecConstLayout[specConstIdx]; - uint dword = specDword(pos.dwordOffset); - return bitfieldExtract(dword, int(pos.bitOffset), int(pos.sizeInBits)); -} - -bool specBool(uint specConstIdx, uint bitOffset) { - return specUint(specConstIdx, bitOffset, 1u) != 0u; -} - -bool specBool(uint specConstIdx) { - return specUint(specConstIdx) != 0u; -} diff --git a/src/d3d9/shaders/d3d9_fixed_function_frag.frag b/src/d3d9/shaders/d3d9_fixed_function_frag.frag deleted file mode 100644 index 50f63c4a9fd..00000000000 --- a/src/d3d9/shaders/d3d9_fixed_function_frag.frag +++ /dev/null @@ -1,4 +0,0 @@ -#version 450 -#extension GL_GOOGLE_include_directive : enable - -#include "d3d9_fixed_function_frag.glsl" diff --git a/src/d3d9/shaders/d3d9_fixed_function_frag.glsl b/src/d3d9/shaders/d3d9_fixed_function_frag.glsl deleted file mode 100644 index f8baf9da2de..00000000000 --- a/src/d3d9/shaders/d3d9_fixed_function_frag.glsl +++ /dev/null @@ -1,722 +0,0 @@ -#extension GL_GOOGLE_include_directive : enable -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_spirv_intrinsics : require -#extension GL_EXT_demote_to_helper_invocation : require -#extension GL_ARB_derivative_control : require -#extension GL_EXT_control_flow_attributes : require -#extension GL_EXT_nonuniform_qualifier : require - - -// The locations need to match with RegisterLinkerSlot in dxso_util.cpp -#ifndef INTERP_MODE -#define INTERP_MODE -#endif - -layout(location = 0) INTERP_MODE in vec4 in_Normal; -layout(location = 1) INTERP_MODE in vec4 in_Texcoord0; -layout(location = 2) INTERP_MODE in vec4 in_Texcoord1; -layout(location = 3) INTERP_MODE in vec4 in_Texcoord2; -layout(location = 4) INTERP_MODE in vec4 in_Texcoord3; -layout(location = 5) INTERP_MODE in vec4 in_Texcoord4; -layout(location = 6) INTERP_MODE in vec4 in_Texcoord5; -layout(location = 7) INTERP_MODE in vec4 in_Texcoord6; -layout(location = 8) INTERP_MODE in vec4 in_Texcoord7; -layout(location = 9) INTERP_MODE in vec4 in_Color0; -layout(location = 10) INTERP_MODE in vec4 in_Color1; -layout(location = 11) INTERP_MODE in float in_Fog; - -layout(location = 0) out vec4 out_Color0; - - -const uint TextureArgCount = 3; -const uint MaxSharedPushDataSize = 64; - -#include "d3d9_fixed_function_common.glsl" - -struct D3D9FFTextureStage { - uint Primitive[2]; -}; - -struct D3D9FixedFunctionPS { - vec4 textureFactor; - D3D9FFTextureStage Stages[8]; -}; - -struct D3D9SharedPSStage { - float Constant[4]; - float BumpEnvMat[2][2]; - float BumpEnvLScale; - float BumpEnvLOffset; - float Padding[2]; -}; - -struct D3D9SharedPS { - D3D9SharedPSStage Stages[TextureStageCount]; -}; - -const uint D3DTOP_DISABLE = 1; -const uint D3DTOP_SELECTARG1 = 2; -const uint D3DTOP_SELECTARG2 = 3; -const uint D3DTOP_MODULATE = 4; -const uint D3DTOP_MODULATE2X = 5; -const uint D3DTOP_MODULATE4X = 6; -const uint D3DTOP_ADD = 7; -const uint D3DTOP_ADDSIGNED = 8; -const uint D3DTOP_ADDSIGNED2X = 9; -const uint D3DTOP_SUBTRACT = 10; -const uint D3DTOP_ADDSMOOTH = 11; -const uint D3DTOP_BLENDDIFFUSEALPHA = 12; -const uint D3DTOP_BLENDTEXTUREALPHA = 13; -const uint D3DTOP_BLENDFACTORALPHA = 14; -const uint D3DTOP_BLENDTEXTUREALPHAPM = 15; -const uint D3DTOP_BLENDCURRENTALPHA = 16; -const uint D3DTOP_PREMODULATE = 17; -const uint D3DTOP_MODULATEALPHA_ADDCOLOR = 18; -const uint D3DTOP_MODULATECOLOR_ADDALPHA = 19; -const uint D3DTOP_MODULATEINVALPHA_ADDCOLOR = 20; -const uint D3DTOP_MODULATEINVCOLOR_ADDALPHA = 21; -const uint D3DTOP_BUMPENVMAP = 22; -const uint D3DTOP_BUMPENVMAPLUMINANCE = 23; -const uint D3DTOP_DOTPRODUCT3 = 24; -const uint D3DTOP_MULTIPLYADD = 25; -const uint D3DTOP_LERP = 26; - -const uint D3DTA_SELECTMASK = 0x0000000f; -const uint D3DTA_DIFFUSE = 0x00000000; -const uint D3DTA_CURRENT = 0x00000001; -const uint D3DTA_TEXTURE = 0x00000002; -const uint D3DTA_TFACTOR = 0x00000003; -const uint D3DTA_SPECULAR = 0x00000004; -const uint D3DTA_TEMP = 0x00000005; -const uint D3DTA_CONSTANT = 0x00000006; -const uint D3DTA_COMPLEMENT = 0x00000010; -const uint D3DTA_ALPHAREPLICATE = 0x00000020; - -const uint D3DRTYPE_SURFACE = 1; -const uint D3DRTYPE_VOLUME = 2; -const uint D3DRTYPE_TEXTURE = 3; -const uint D3DRTYPE_VOLUMETEXTURE = 4; -const uint D3DRTYPE_CUBETEXTURE = 5; -const uint D3DRTYPE_VERTEXBUFFER = 6; -const uint D3DRTYPE_INDEXBUFFER = 7; - -const uint VK_COMPARE_OP_NEVER = 0; -const uint VK_COMPARE_OP_LESS = 1; -const uint VK_COMPARE_OP_EQUAL = 2; -const uint VK_COMPARE_OP_LESS_OR_EQUAL = 3; -const uint VK_COMPARE_OP_GREATER = 4; -const uint VK_COMPARE_OP_NOT_EQUAL = 5; -const uint VK_COMPARE_OP_GREATER_OR_EQUAL = 6; -const uint VK_COMPARE_OP_ALWAYS = 7; - -const uint PerTextureStageSpecConsts = SpecFFTextureStage1ColorOp - SpecFFTextureStage0ColorOp; - - -// Bindings have to match with computeResourceSlotId in dxso_util.h -// computeResourceSlotId( -// DxsoProgramType::PixelShader, -// DxsoBindingType::ConstantBuffer, -// DxsoConstantBuffers::PSFixedFunction -// ) = 11 -layout(set = 0, binding = 11, scalar, row_major) uniform ShaderData { - D3D9FixedFunctionPS data; -}; - -// Bindings have to match with computeResourceSlotId in dxso_util.h -// computeResourceSlotId( -// DxsoProgramType::PixelShader, -// DxsoBindingType::ConstantBuffer, -// DxsoConstantBuffers::PSShared -// ) = 12 -layout(set = 0, binding = 12, scalar, row_major) uniform SharedData { - D3D9SharedPS sharedData; -}; - -layout(push_constant, scalar, row_major) uniform RenderStates { - D3D9RenderStateInfo rs; - - layout(offset = MaxSharedPushDataSize) uint packedSamplerIndices[TextureStageCount / 2]; -}; - -layout(set = 0, binding = 13) uniform texture2D t2d[TextureStageCount]; -layout(set = 0, binding = 13) uniform textureCube tcube[TextureStageCount]; -layout(set = 0, binding = 13) uniform texture3D t3d[TextureStageCount]; - -layout(set = 1, binding = 0) uniform sampler sampler_heap[]; - - -// Functions to extract information from the packed texture stages -uint colorOp(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 0, 5); -} -uint colorArg0(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 5, 6); -} -uint colorArg1(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 11, 6); -} -uint colorArg2(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 17, 6); -} - -uint alphaOp(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[0], 23, 5); -} -uint alphaArg0(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[1], 0, 6); -} -uint alphaArg1(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[1], 6, 6); -} -uint alphaArg2(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[1], 12, 6); -} - -bool resultIsTemp(uint stageIndex) { - return bitfieldExtract(data.Stages[stageIndex].Primitive[1], 18, 1) != 0; -} - - -vec4 calculateFog(vec4 vPos, vec4 oColor) { - vec3 fogColor = vec3(rs.fogColor[0], rs.fogColor[1], rs.fogColor[2]); - float fogScale = rs.fogScale; - float fogEnd = rs.fogEnd; - float fogDensity = rs.fogDensity; - D3DFOGMODE fogMode = specUint(SpecPixelFogMode); - bool fogEnabled = specBool(SpecFogEnabled); - if (!fogEnabled) { - return oColor; - } - - float w = vPos.w; - float z = vPos.z; - float depth = z * (1.0 / w); - float fogFactor; - switch (fogMode) { - case D3DFOG_NONE: - fogFactor = in_Fog; - break; - - // (end - d) / (end - start) - case D3DFOG_LINEAR: - fogFactor = fogEnd - depth; - fogFactor = fogFactor * fogScale; - fogFactor = spvNClamp(fogFactor, 0.0, 1.0); - break; - - // 1 / (e^[d * density])^2 - case D3DFOG_EXP2: - // 1 / (e^[d * density]) - case D3DFOG_EXP: - fogFactor = depth * fogDensity; - - if (fogMode == D3DFOG_EXP2) - fogFactor *= fogFactor; - - // Provides the rcp. - fogFactor = -fogFactor; - fogFactor = exp(fogFactor); - break; - } - - vec4 color = oColor; - vec3 color3 = color.rgb; - vec3 fogFact3 = vec3(fogFactor); - vec3 lerpedFrog = mix(fogColor, color3, fogFact3); - return vec4(lerpedFrog.r, lerpedFrog.g, lerpedFrog.b, color.a); -} - - -// [D3D8] Scale Dref to [0..(2^N - 1)] for D24S8 and D16 if Dref scaling is enabled -float adjustDref(float reference, uint samplerIndex) { - uint drefScaleFactor = specUint(SpecDrefScaling); - if (drefScaleFactor != 0) { - float maxDref = 1.0 / (float(1 << drefScaleFactor) - 1.0); - reference *= maxDref; - } - if (specBool(SpecSamplerDrefClamp, samplerIndex)) { - reference = clamp(reference, 0.0, 1.0); - } - return reference; -} - - -vec4 calculateBumpmapCoords(uint stage, vec4 baseCoords, vec4 previousStageTextureVal) { - uint previousStage = stage - 1; - - vec4 coords = baseCoords; - [[unroll]] - for (uint i = 0; i < 2; i++) { - float tc_m_n = coords[i]; - vec2 bm = vec2(sharedData.Stages[previousStage].BumpEnvMat[i][0], sharedData.Stages[previousStage].BumpEnvMat[i][1]); - vec2 t = previousStageTextureVal.xy; - float result = tc_m_n + dot(bm, t); - coords[i] = result; - } - return coords; -} - - -uint loadSamplerHeapIndex(uint samplerBindingIndex) { - uint packedSamplerIndex = packedSamplerIndices[samplerBindingIndex / 2u]; - return bitfieldExtract(packedSamplerIndex, 16 * (int(samplerBindingIndex) & 1), 16); -} - - -vec4 sampleTexture(uint stage, vec4 texcoord, vec4 previousStageTextureVal) { - if (specBool(SpecSamplerProjected, stage)) { - texcoord /= texcoord.w; - } - - uint previousStageColorOp = 0; - if (stage > 0) { - previousStageColorOp = specIsOptimized() ? specUint(SpecFFTextureStage0ColorOp + PerTextureStageSpecConsts * (stage - 1)) : colorOp(stage - 1); - } - - if (stage != 0 && ( - previousStageColorOp == D3DTOP_BUMPENVMAP - || previousStageColorOp == D3DTOP_BUMPENVMAPLUMINANCE)) { - texcoord = calculateBumpmapCoords(stage, texcoord, previousStageTextureVal); - } - - vec4 texVal; - uint textureType = D3DRTYPE_TEXTURE + specUint(SpecSamplerType, 2u * stage, 2u); - switch (textureType) { - case D3DRTYPE_TEXTURE: - if (specBool(SpecSamplerDepthMode, stage)) { - texcoord.z = adjustDref(texcoord.z, stage); - texVal = texture(sampler2DShadow(t2d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord.xyz).xxxx; - } else { - if (!specBool(SpecFFColorKeyCompatibility) && specBool(SpecFFColorKeyEnabled)) { - const ivec2 texSize = textureSize(sampler2D(t2d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), 0); - const ivec2 pixelCoord = ivec2(texcoord.xy * vec2(texSize)); - texVal = texelFetch(sampler2D(t2d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), pixelCoord, 0); - const float ckrl = bitfieldExtract(specUint(SpecFFColorKeyLow), 0, 8); - const float ckgl = bitfieldExtract(specUint(SpecFFColorKeyLow), 8, 8); - const float ckbl = bitfieldExtract(specUint(SpecFFColorKeyLow), 16, 8); - const float ckrh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 0, 8); - const float ckgh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 8, 8); - const float ckbh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 16, 8); - const ivec3 src = ivec3(texVal.rgb * 255.0); - if (src.r >= ckrl && src.g >= ckgl && src.b >= ckbl && - src.r <= ckrh && src.g <= ckgh && src.b <= ckbh) { - discard; - } - } - - texVal = texture(sampler2D(t2d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord.xy); - } - break; - case D3DRTYPE_CUBETEXTURE: - if (specBool(SpecSamplerDepthMode, stage)) { - texcoord.w = adjustDref(texcoord.w, stage); - texVal = texture(samplerCubeShadow(tcube[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord).xxxx; - } else { - texVal = texture(samplerCube(tcube[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord.xyz); - } - break; - case D3DRTYPE_VOLUMETEXTURE: - texVal = texture(sampler3D(t3d[stage], sampler_heap[loadSamplerHeapIndex(stage)]), texcoord.xyz); - break; - default: - // This should never happen unless there's a major bug in the API implementation. - // Produce a value that's obviously wrong to make it obvious when it somehow does happen. - texVal = vec4(999.9); - break; - } - - if (specBool(SpecFFColorKeyCompatibility) && specBool(SpecFFColorKeyEnabled)) { - const float ckrl = bitfieldExtract(specUint(SpecFFColorKeyLow), 0, 8); - const float ckgl = bitfieldExtract(specUint(SpecFFColorKeyLow), 8, 8); - const float ckbl = bitfieldExtract(specUint(SpecFFColorKeyLow), 16, 8); - const float ckrh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 0, 8); - const float ckgh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 8, 8); - const float ckbh = bitfieldExtract(specUint(SpecFFColorKeyHigh), 16, 8); - const ivec3 src = ivec3(texVal.rgb * 255.0); - if (src.r >= ckrl && src.g >= ckgl && src.b >= ckbl && - src.r <= ckrh && src.g <= ckgh && src.b <= ckbh) { - discard; - } - } - - if (stage != 0 && previousStageColorOp == D3DTOP_BUMPENVMAPLUMINANCE) { - float lScale = sharedData.Stages[stage - 1].BumpEnvLScale; - float lOffset = sharedData.Stages[stage - 1].BumpEnvLOffset; - float scale = texVal.z; - scale *= lScale; - scale += lOffset; - scale = clamp(scale, 0.0, 1.0); - texVal *= scale; - } - - return texVal; -} - - -vec4 readArgValue(uint stage, uint arg, vec4 current, vec4 temp, vec4 textureVal) { - vec4 reg = vec4(1.0); - switch (arg & D3DTA_SELECTMASK) { - case D3DTA_CONSTANT: - reg = vec4( - sharedData.Stages[stage].Constant[0], - sharedData.Stages[stage].Constant[1], - sharedData.Stages[stage].Constant[2], - sharedData.Stages[stage].Constant[3] - ); - break; - case D3DTA_CURRENT: - reg = current; - break; - case D3DTA_DIFFUSE: - reg = in_Color0; - break; - case D3DTA_SPECULAR: - reg = in_Color1; - break; - case D3DTA_TEMP: - reg = temp; - break; - case D3DTA_TEXTURE: - reg = textureVal; - break; - case D3DTA_TFACTOR: - reg = data.textureFactor; - break; - } - - // reg = 1 - reg - if ((arg & D3DTA_COMPLEMENT) != 0) - reg = vec4(1.0) - reg; - - // reg = reg.wwww - if ((arg & D3DTA_ALPHAREPLICATE) != 0) - reg = reg.aaaa; - - return reg; -} - -struct TextureStageArguments { - uint arg0; - uint arg1; - uint arg2; -}; - -struct TextureStageArgumentValues { - vec4 arg0; - vec4 arg1; - vec4 arg2; -}; - -TextureStageArgumentValues readArgValues(uint stage, const TextureStageArguments args, vec4 current, vec4 temp, vec4 textureVal) { - TextureStageArgumentValues argVals; - argVals.arg0 = readArgValue(stage, args.arg0, current, temp, textureVal); - argVals.arg1 = readArgValue(stage, args.arg1, current, temp, textureVal); - argVals.arg2 = readArgValue(stage, args.arg2, current, temp, textureVal); - return argVals; -} - -uint repackArg(uint arg) { - // Move the flags by 1 bit. 0x18 = 0b11000 - return (arg & ~0x18) | ((arg & 0x18) << 1u); -} - -vec4 complement(vec4 val) { - return vec4(1.0) - val; -} - -vec4 saturate(vec4 val) { - return clamp(val, vec4(0.0), vec4(1.0)); -} - -vec4 calculateTextureStage(uint op, vec4 dst, const TextureStageArgumentValues arg, vec4 current, vec4 textureVal) { - switch (op) { - case D3DTOP_SELECTARG1: - return arg.arg1; - - case D3DTOP_SELECTARG2: - return arg.arg2; - - case D3DTOP_MODULATE4X: - return arg.arg1 * arg.arg2 * 4.0; - - case D3DTOP_MODULATE2X: - return arg.arg1 * arg.arg2 * 2.0; - - case D3DTOP_MODULATE: - return arg.arg1 * arg.arg2; - - case D3DTOP_ADDSIGNED2X: - return saturate(2.0 * (arg.arg1 + (arg.arg2 - vec4(0.5)))); - - case D3DTOP_ADDSIGNED: - return saturate(arg.arg1 + (arg.arg2 - vec4(0.5))); - - case D3DTOP_ADD: - return saturate(arg.arg1 + arg.arg2); - - case D3DTOP_SUBTRACT: - return saturate(arg.arg1 - arg.arg2); - - case D3DTOP_ADDSMOOTH: - return fma(complement(arg.arg1), arg.arg2, arg.arg1); - - case D3DTOP_BLENDDIFFUSEALPHA: - return mix(arg.arg2, arg.arg1, in_Color0.aaaa); - - case D3DTOP_BLENDTEXTUREALPHA: - return mix(arg.arg2, arg.arg1, textureVal.aaaa); - - case D3DTOP_BLENDFACTORALPHA: - return mix(arg.arg2, arg.arg1, data.textureFactor.aaaa); - - case D3DTOP_BLENDTEXTUREALPHAPM: - return saturate(fma(arg.arg2, complement(textureVal.aaaa), arg.arg1)); - - case D3DTOP_BLENDCURRENTALPHA: - return mix(arg.arg2, arg.arg1, current.aaaa); - - case D3DTOP_PREMODULATE: - return dst; // Not implemented - - case D3DTOP_MODULATEALPHA_ADDCOLOR: - return saturate(fma(arg.arg1.aaaa, arg.arg2, arg.arg1)); - - case D3DTOP_MODULATECOLOR_ADDALPHA: - return saturate(fma(arg.arg1, arg.arg2, arg.arg1.aaaa)); - - case D3DTOP_MODULATEINVALPHA_ADDCOLOR: - return saturate(fma(complement(arg.arg1.aaaa), arg.arg2, arg.arg1)); - - case D3DTOP_MODULATEINVCOLOR_ADDALPHA: - return saturate(fma(complement(arg.arg1), arg.arg2, arg.arg1.aaaa)); - - case D3DTOP_BUMPENVMAPLUMINANCE: - case D3DTOP_BUMPENVMAP: - // Load texture for the next stage... - return dst; - - case D3DTOP_DOTPRODUCT3: - return saturate(vec4(dot(arg.arg1.rgb - vec3(0.5), arg.arg2.rgb - vec3(0.5)) * 4.0)); - - case D3DTOP_MULTIPLYADD: - return saturate(fma(arg.arg1, arg.arg2, arg.arg0)); - - case D3DTOP_LERP: - return mix(arg.arg2, arg.arg1, arg.arg0); - - default: - // Unhandled texture op! - return dst; - - } - - return vec4(0.0); -} - - -void alphaTest() { - uint alphaFunc = specUint(SpecAlphaCompareOp); - uint alphaPrecision = specUint(SpecAlphaPrecisionBits); - uint alphaRefInitial = rs.alphaRef; - float alphaRef; - float alpha = out_Color0.a; - - if (alphaFunc == VK_COMPARE_OP_ALWAYS) { - return; - } - - // Check if the given bit precision is supported - bool useIntPrecision = alphaPrecision <= 8; - if (useIntPrecision) { - // Adjust alpha ref to the given range - uint alphaRefInt = (alphaRefInitial << alphaPrecision) | (alphaRefInitial >> (8 - alphaPrecision)); - - // Convert alpha ref to float since we'll do the comparison based on that - alphaRef = float(alphaRefInt); - - // Adjust alpha to the given range and round - float alphaFactor = float((256u << alphaPrecision) - 1u); - - alpha = round(alpha * alphaFactor); - } else { - alphaRef = float(alphaRefInitial) / 255.0; - } - - bool atestResult; - switch (alphaFunc) { - case VK_COMPARE_OP_NEVER: - atestResult = false; - break; - - case VK_COMPARE_OP_LESS: - atestResult = alpha < alphaRef; - break; - - case VK_COMPARE_OP_EQUAL: - atestResult = alpha == alphaRef; - break; - - case VK_COMPARE_OP_LESS_OR_EQUAL: - atestResult = alpha <= alphaRef; - break; - - case VK_COMPARE_OP_GREATER: - atestResult = alpha > alphaRef; - break; - - case VK_COMPARE_OP_NOT_EQUAL: - atestResult = alpha != alphaRef; - break; - - case VK_COMPARE_OP_GREATER_OR_EQUAL: - atestResult = alpha >= alphaRef; - break; - - default: - case VK_COMPARE_OP_ALWAYS: - atestResult = true; - break; - } - - bool atestDiscard = !atestResult; - if (atestDiscard) { - demote; - } -} - -struct TextureStageState { - vec4 current; - vec4 temp; - vec4 previousStageTextureVal; -}; - -TextureStageState runTextureStage(uint stage, TextureStageState state) { - if (stage > specUint(SpecFFLastActiveTextureStage)) { - return state; - } - - const uint colorOp = specIsOptimized() ? specUint(SpecFFTextureStage0ColorOp + PerTextureStageSpecConsts * stage) : colorOp(stage); - - // This cancels all subsequent stages. - if (colorOp == D3DTOP_DISABLE) - return state; - - const bool resultIsTemp = specIsOptimized() ? specBool(SpecFFTextureStage0ResultIsTemp + PerTextureStageSpecConsts * stage) : resultIsTemp(stage); - vec4 dst = resultIsTemp ? state.temp : state.current; - - const uint alphaOp = specIsOptimized() ? specUint(SpecFFTextureStage0AlphaOp + PerTextureStageSpecConsts * stage) : alphaOp(stage); - - const TextureStageArguments colorArgs = { - // Color arg0 and alpha arg0 for all stages are packed after all the other FF spec consts - specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0ColorArg0 + stage)) : colorArg0(stage), - specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0ColorArg1 + PerTextureStageSpecConsts * stage)) : colorArg1(stage), - specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0ColorArg2 + PerTextureStageSpecConsts * stage)) : colorArg2(stage) - }; - const TextureStageArguments alphaArgs = { - // Color arg0 and alpha arg0 for all stages are packed after all the other FF spec consts - specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0AlphaArg0 + stage)) : alphaArg0(stage), - specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0AlphaArg1 + PerTextureStageSpecConsts * stage)) : alphaArg1(stage), - specIsOptimized() ? repackArg(specUint(SpecFFTextureStage0AlphaArg2 + PerTextureStageSpecConsts * stage)) : alphaArg2(stage) - }; - - vec4 textureVal = vec4(0.0); - bool usesTexture = (colorArgs.arg0 & D3DTA_SELECTMASK) == D3DTA_TEXTURE - || (colorArgs.arg1 & D3DTA_SELECTMASK) == D3DTA_TEXTURE - || (colorArgs.arg2 & D3DTA_SELECTMASK) == D3DTA_TEXTURE - || (alphaArgs.arg0 & D3DTA_SELECTMASK) == D3DTA_TEXTURE - || (alphaArgs.arg1 & D3DTA_SELECTMASK) == D3DTA_TEXTURE - || (alphaArgs.arg2 & D3DTA_SELECTMASK) == D3DTA_TEXTURE; - - if (usesTexture) { - // We need to replace TEXCOORD inputs with gl_PointCoord - // if D3DRS_POINTSPRITEENABLE is set. - const uint pointMode = specUint(SpecPointMode); - const bool isSprite = bitfieldExtract(pointMode, 1, 1) == 1u; - - vec4 texCoord; - if (isSprite) { - texCoord = vec4(gl_PointCoord, 0.0, 0.0); - } else { - switch (stage) { - case 0: texCoord = in_Texcoord0; break; - case 1: texCoord = in_Texcoord1; break; - case 2: texCoord = in_Texcoord2; break; - case 3: texCoord = in_Texcoord3; break; - case 4: texCoord = in_Texcoord4; break; - case 5: texCoord = in_Texcoord5; break; - case 6: texCoord = in_Texcoord6; break; - case 7: texCoord = in_Texcoord7; break; - } - } - const vec4 unboundTextureConst = vec4(0.0, 0.0, 0.0, 1.0); - textureVal = !specBool(SpecSamplerNull, stage) ? sampleTexture(stage, texCoord, state.previousStageTextureVal) : unboundTextureConst; - } - - // Fast path if alpha/color path is identical. - // D3DTOP_DOTPRODUCT3 also has special quirky behaviour here. - const bool fastPath = colorOp == alphaOp && colorArgs == alphaArgs; - if (fastPath || colorOp == D3DTOP_DOTPRODUCT3) { - TextureStageArgumentValues colorArgVals = readArgValues(stage, colorArgs, state.current, state.temp, textureVal); - dst = calculateTextureStage(colorOp, dst, colorArgVals, state.current, textureVal); - } else { - vec4 colorResult = dst; - vec4 alphaResult = dst; - - TextureStageArgumentValues colorArgVals = readArgValues(stage, colorArgs, state.current, state.temp, textureVal); - colorResult = calculateTextureStage(colorOp, dst, colorArgVals, state.current, textureVal); - - if (alphaOp != D3DTOP_DISABLE) { - TextureStageArgumentValues alphaArgVals = readArgValues(stage, alphaArgs, state.current, state.temp, textureVal); - alphaResult = calculateTextureStage(alphaOp, dst, alphaArgVals, state.current, textureVal); - } - - dst.xyz = colorResult.xyz; - - // src0.x, src0.y, src0.z src1.w - if (alphaOp != D3DTOP_DISABLE) { - dst.a = alphaResult.a; - } - } - - if (resultIsTemp) { - state.temp = dst; - } else { - state.current = dst; - } - state.previousStageTextureVal = textureVal; - - return state; -} - -void main() { - // in_Color0 is diffuse - // in_Color1 is specular - - TextureStageState state; - // Current starts of as equal to diffuse. - state.current = in_Color0; - // Temp starts off as equal to vec4(0) - state.temp = vec4(0.0); - state.previousStageTextureVal = vec4(0.0); - - // If we turn this into a loop, performance becomes very poor on the proprietary Nvidia driver - // because it fails to unroll it. - state = runTextureStage(0, state); - state = runTextureStage(1, state); - state = runTextureStage(2, state); - state = runTextureStage(3, state); - state = runTextureStage(4, state); - state = runTextureStage(5, state); - state = runTextureStage(6, state); - state = runTextureStage(7, state); - - if (specBool(SpecFFGlobalSpecularEnabled)) { - state.current.xyz += in_Color1.xyz; - } - - state.current = calculateFog(gl_FragCoord, state.current); - - out_Color0 = state.current; - - alphaTest(); -} diff --git a/src/d3d9/shaders/d3d9_fixed_function_frag_sample.frag b/src/d3d9/shaders/d3d9_fixed_function_frag_sample.frag deleted file mode 100644 index 807f90b8749..00000000000 --- a/src/d3d9/shaders/d3d9_fixed_function_frag_sample.frag +++ /dev/null @@ -1,6 +0,0 @@ -#version 450 -#extension GL_GOOGLE_include_directive : enable - -#define INTERP_MODE sample - -#include "d3d9_fixed_function_frag.glsl" diff --git a/src/d3d9/shaders/d3d9_fixed_function_vert.vert b/src/d3d9/shaders/d3d9_fixed_function_vert.vert deleted file mode 100644 index 1504405a307..00000000000 --- a/src/d3d9/shaders/d3d9_fixed_function_vert.vert +++ /dev/null @@ -1,725 +0,0 @@ -#version 450 -#extension GL_GOOGLE_include_directive : enable -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_spirv_intrinsics : require - -layout(location = 0) in vec4 in_Position0; -layout(location = 1) in vec4 in_Normal0; -layout(location = 2) in vec4 in_Position1; -layout(location = 3) in vec4 in_Normal1; -layout(location = 4) in vec4 in_Texcoord0; -layout(location = 5) in vec4 in_Texcoord1; -layout(location = 6) in vec4 in_Texcoord2; -layout(location = 7) in vec4 in_Texcoord3; -layout(location = 8) in vec4 in_Texcoord4; -layout(location = 9) in vec4 in_Texcoord5; -layout(location = 10) in vec4 in_Texcoord6; -layout(location = 11) in vec4 in_Texcoord7; -layout(location = 12) in vec4 in_Color0; -layout(location = 13) in vec4 in_Color1; -layout(location = 14) in float in_Fog; -layout(location = 15) in float in_PointSize; -layout(location = 16) in vec4 in_BlendWeight; -layout(location = 17) in vec4 in_BlendIndices; - - -// The locations need to match with RegisterLinkerSlot in dxso_util.cpp -precise gl_Position; -const uint MaxClipPlaneCount = 6; -out float gl_ClipDistance[MaxClipPlaneCount]; -layout(location = 0) out vec4 out_Normal; -layout(location = 1) out vec4 out_Texcoord0; -layout(location = 2) out vec4 out_Texcoord1; -layout(location = 3) out vec4 out_Texcoord2; -layout(location = 4) out vec4 out_Texcoord3; -layout(location = 5) out vec4 out_Texcoord4; -layout(location = 6) out vec4 out_Texcoord5; -layout(location = 7) out vec4 out_Texcoord6; -layout(location = 8) out vec4 out_Texcoord7; -layout(location = 9) out vec4 out_Color0; -layout(location = 10) out vec4 out_Color1; -layout(location = 11) out float out_Fog; - - -#include "d3d9_fixed_function_common.glsl" - -const uint MaxEnabledLights = 8; - -struct D3D9ViewportInfo { - vec4 inverseOffset; - vec4 inverseExtent; -}; - -#define D3DLIGHTTYPE uint -const uint D3DLIGHT_POINT = 1; -const uint D3DLIGHT_SPOT = 2; -const uint D3DLIGHT_DIRECTIONAL = 3; - -struct D3D9Light { - vec4 Diffuse; - vec4 Specular; - vec4 Ambient; - - vec4 Position; - vec4 Direction; - - D3DLIGHTTYPE Type; - float Range; - float Falloff; - float Attenuation0; - float Attenuation1; - float Attenuation2; - float Theta; - float Phi; -}; - -#define D3DCOLORVALUE vec4 - -struct D3DMATERIAL9 { - D3DCOLORVALUE Diffuse; - D3DCOLORVALUE Ambient; - D3DCOLORVALUE Specular; - D3DCOLORVALUE Emissive; - float Power; -}; - -struct D3D9FixedFunctionVS { - mat4 WorldView; - mat4 NormalMatrix; - mat4 InverseView; - mat4 Projection; - - mat4 TexcoordMatrices[TextureStageCount]; - - D3D9ViewportInfo ViewportInfo; - - vec4 GlobalAmbient; - D3D9Light Lights[MaxEnabledLights]; - D3DMATERIAL9 Material; - float TweenFactor; - - uint KeyPrimitives[4]; -}; - -#define D3D9FF_VertexBlendMode uint -const uint D3D9FF_VertexBlendMode_Disabled = 0; -const uint D3D9FF_VertexBlendMode_Normal = 1; -const uint D3D9FF_VertexBlendMode_Tween = 2; - -#define D3DMATERIALCOLORSOURCE uint -const uint D3DMCS_MATERIAL = 0; -const uint D3DMCS_COLOR1 = 1; -const uint D3DMCS_COLOR2 = 2; - -#define D3DTEXTURETRANSFORMFLAGS uint -const uint D3DTTFF_DISABLE = 0; -const uint D3DTTFF_COUNT1 = 1; -const uint D3DTTFF_COUNT2 = 2; -const uint D3DTTFF_COUNT3 = 3; -const uint D3DTTFF_COUNT4 = 4; -const uint D3DTTFF_PROJECTED = 256; - -const uint DXVK_TSS_TCI_PASSTHRU = 0x00000000; -const uint DXVK_TSS_TCI_CAMERASPACENORMAL = 0x00010000; -const uint DXVK_TSS_TCI_CAMERASPACEPOSITION = 0x00020000; -const uint DXVK_TSS_TCI_CAMERASPACEREFLECTIONVECTOR = 0x00030000; -const uint DXVK_TSS_TCI_SPHEREMAP = 0x00040000; - -const uint TCIOffset = 16; -const uint TCIMask = (7 << TCIOffset); - - -// Bindings have to match with computeResourceSlotId in dxso_util.h -// computeResourceSlotId( -// DxsoProgramType::VertexShader, -// DxsoBindingType::ConstantBuffer, -// DxsoConstantBuffers::VSFixedFunction -// ) = 4 -layout(set = 0, binding = 4, scalar, row_major) uniform ShaderData { - D3D9FixedFunctionVS data; -}; - -layout(push_constant, scalar, row_major) uniform RenderStates { - D3D9RenderStateInfo rs; -}; - -// Bindings have to match with computeResourceSlotId in dxso_util.h -// computeResourceSlotId( -// DxsoProgramType::VertexShader, -// DxsoBindingType::ConstantBuffer, -// DxsoConstantBuffers::VSVertexBlendData -// ) = 5 -layout(set = 0, binding = 5, std140, row_major) readonly buffer VertexBlendData { - mat4 WorldViewArray[]; -}; - - -// Bindings have to match with computeResourceSlotId in dxso_util.h -// computeResourceSlotId( -// DxsoProgramType::VertexShader, -// DxsoBindingType::ConstantBuffer, -// DxsoConstantBuffers::VSClipPlanes -// ) = 3 -layout(set = 0, binding = 3, std140) uniform ClipPlanes { - vec4 clipPlanes[MaxClipPlaneCount]; -}; - - -// Functions to extract information from the packed VS key -// See D3D9FFShaderKeyVSData in d3d9_shader_types.h -// Please, dearest compiler, inline all of this. -uint texcoordIndices() { - return bitfieldExtract(data.KeyPrimitives[0], 0, 24); -} -bool vertexHasPositionT() { - return bitfieldExtract(data.KeyPrimitives[0], 24, 1) != 0; -} -bool vertexHasColor0() { - return bitfieldExtract(data.KeyPrimitives[0], 25, 1) != 0; -} -bool vertexHasColor1() { - return bitfieldExtract(data.KeyPrimitives[0], 26, 1) != 0; -} -bool vertexHasPointSize() { - return bitfieldExtract(data.KeyPrimitives[0], 27, 1) != 0; -} -bool useLighting() { - return bitfieldExtract(data.KeyPrimitives[0], 28, 1) != 0; -} -bool normalizeNormals() { - return bitfieldExtract(data.KeyPrimitives[0], 29, 1) != 0; -} -bool localViewer() { - return bitfieldExtract(data.KeyPrimitives[0], 30, 1) != 0; -} -bool rangeFog() { - return bitfieldExtract(data.KeyPrimitives[0], 31, 1) != 0; -} - -uint texcoordFlags() { - return bitfieldExtract(data.KeyPrimitives[1], 0, 24); -} -uint diffuseSource() { - return bitfieldExtract(data.KeyPrimitives[1], 24, 2); -} -uint ambientSource() { - return bitfieldExtract(data.KeyPrimitives[1], 26, 2); -} -uint specularSource() { - return bitfieldExtract(data.KeyPrimitives[1], 28, 2); -} -uint emissiveSource() { - return bitfieldExtract(data.KeyPrimitives[1], 30, 2); -} - -uint transformFlags() { - return bitfieldExtract(data.KeyPrimitives[2], 0, 24); -} -uint lightCount() { - return bitfieldExtract(data.KeyPrimitives[2], 24, 4); -} -bool specularEnabled() { - return bitfieldExtract(data.KeyPrimitives[2], 28, 1) != 0; -} - -uint vertexTexcoordDeclMask() { - return bitfieldExtract(data.KeyPrimitives[3], 0, 24); -} -bool vertexHasFog() { - return bitfieldExtract(data.KeyPrimitives[3], 24, 1) != 0; -} -D3D9FF_VertexBlendMode blendMode() { - return bitfieldExtract(data.KeyPrimitives[3], 25, 2); -} -bool vertexBlendIndexed() { - return bitfieldExtract(data.KeyPrimitives[3], 27, 1) != 0; -} -uint vertexBlendCount() { - return bitfieldExtract(data.KeyPrimitives[3], 28, 2); -} -bool vertexClipping() { - return bitfieldExtract(data.KeyPrimitives[3], 30, 1) != 0; -} - - -float calculateFog(vec4 vPos, vec4 oColor) { - vec4 specular = in_Color1; - bool hasSpecular = vertexHasColor1(); - - vec3 fogColor = vec3(rs.fogColor[0], rs.fogColor[1], rs.fogColor[2]); - float fogScale = rs.fogScale; - float fogEnd = rs.fogEnd; - float fogDensity = rs.fogDensity; - D3DFOGMODE fogMode = specUint(SpecVertexFogMode); - bool fogEnabled = specBool(SpecFogEnabled); - if (!fogEnabled) { - return 0.0; - } - - float w = vPos.w; - float z = vPos.z; - float depth; - if (rangeFog()) { - vec3 pos3 = vPos.xyz; - depth = length(pos3); - } else { - depth = vertexHasFog() ? in_Fog : abs(z); - } - float fogFactor; - if (vertexHasPositionT()) { - fogFactor = hasSpecular ? specular.w : 1.0; - } else { - switch (fogMode) { - case D3DFOG_NONE: - fogFactor = hasSpecular ? specular.w : 1.0; - break; - - // (end - d) / (end - start) - case D3DFOG_LINEAR: - fogFactor = fogEnd - depth; - fogFactor = fogFactor * fogScale; - fogFactor = spvNClamp(fogFactor, 0.0, 1.0); - break; - - // 1 / (e^[d * density])^2 - case D3DFOG_EXP2: - // 1 / (e^[d * density]) - case D3DFOG_EXP: - fogFactor = depth * fogDensity; - - if (fogMode == D3DFOG_EXP2) - fogFactor *= fogFactor; - - // Provides the rcp. - fogFactor = -fogFactor; - fogFactor = exp(fogFactor); - break; - } - } - - return fogFactor; -} - - -float calculatePointSize(vec4 vtx) { - float value = vertexHasPointSize() ? in_PointSize : rs.pointSize; - uint pointMode = specUint(SpecPointMode); - bool isScale = bitfieldExtract(pointMode, 0, 1) != 0; - float scaleC = rs.pointScaleC; - float scaleB = rs.pointScaleB; - float scaleA = rs.pointScaleA; - - vec3 vtx3 = vtx.xyz; - - float DeSqr = dot(vtx3, vtx3); - float De = sqrt(DeSqr); - float scaleValue = scaleC * DeSqr; - scaleValue = fma(scaleB, De, scaleValue); - scaleValue += scaleA; - scaleValue = sqrt(scaleValue); - scaleValue = value / scaleValue; - - value = isScale ? scaleValue : value; - - float pointSizeMin = rs.pointSizeMin; - float pointSizeMax = rs.pointSizeMax; - - return clamp(value, pointSizeMin, pointSizeMax); -} - - -void emitVsClipping(vec4 vtx) { - vec4 worldPos = data.InverseView * vtx; - - // Always consider clip planes enabled when doing GPL by forcing 6 for the quick value. - uint clipPlaneCount = specUint(SpecClipPlaneCount); - - // Compute clip distances - for (uint i = 0; i < MaxClipPlaneCount; i++) { - vec4 clipPlane = clipPlanes[i]; - float dist = dot(worldPos, clipPlane); - bool clipPlaneEnabled = i < clipPlaneCount; - float value = clipPlaneEnabled ? dist : 0.0; - gl_ClipDistance[i] = value; - } -} - - -vec4 pickMaterialSource(uint source, vec4 material) { - if (source == D3DMCS_COLOR1 && vertexHasColor0()) - return in_Color0; - else if (source == D3DMCS_COLOR2 && vertexHasColor1()) - return in_Color1; - else - return material; -} - - -void main() { - vec4 vtx = in_Position0; - gl_Position = in_Position0; - vec3 normal = in_Normal0.xyz; - - if (blendMode() == D3D9FF_VertexBlendMode_Tween) { - vec4 vtx1 = in_Position1; - vec3 normal1 = in_Normal1.xyz; - vtx = mix(vtx, vtx1, data.TweenFactor); - normal = mix(normal, normal1, data.TweenFactor); - } - - if (!vertexHasPositionT()) { - if (blendMode() == D3D9FF_VertexBlendMode_Normal) { - float blendWeightRemaining = 1.0; - vec4 vtxSum = vec4(0.0); - vec3 nrmSum = vec3(0.0); - - for (uint i = 0; i <= vertexBlendCount(); i++) { - uint arrayIndex; - if (vertexBlendIndexed()) { - arrayIndex = uint(round(in_BlendIndices[i])); - } else { - arrayIndex = i; - } - mat4 worldView = WorldViewArray[arrayIndex]; - - mat3 nrmMtx; - for (uint j = 0; j < 3; j++) { - nrmMtx[j] = worldView[j].xyz; - } - - vec4 vtxResult = vtx * worldView; - vec3 nrmResult = normal * nrmMtx; - - float weight; - if (i != vertexBlendCount()) { - weight = in_BlendWeight[i]; - blendWeightRemaining -= weight; - } else { - weight = blendWeightRemaining; - } - - vec4 weightVec4 = vec4(weight, weight, weight, weight); - - vtxSum = fma(vtxResult, weightVec4, vtxSum); - nrmSum = fma(nrmResult, weightVec4.xyz, nrmSum); - } - - vtx = vtxSum; - normal = nrmSum; - } else { - vtx = vtx * data.WorldView; - - mat3 nrmMtx = mat3(data.NormalMatrix); - - normal = nrmMtx * normal; - } - - // Some games rely on normals not being normal. - if (normalizeNormals()) { - bool isZeroNormal = all(equal(normal, vec3(0.0, 0.0, 0.0))); - normal = isZeroNormal ? normal : normalize(normal); - } - - gl_Position = vtx * data.Projection; - } else { - gl_Position *= data.ViewportInfo.inverseExtent; - gl_Position += data.ViewportInfo.inverseOffset; - - // We still need to account for perspective correction here... - - float w = gl_Position.w; - float rhw = w == 0.0 ? 1.0 : 1.0 / w; - gl_Position.xyz *= rhw; - gl_Position.w = rhw; - } - - vec4 outNrm = vec4(normal, 1.0); - out_Normal = outNrm; - - vec4 texCoords[TextureStageCount]; - texCoords[0] = in_Texcoord0; - texCoords[1] = in_Texcoord1; - texCoords[2] = in_Texcoord2; - texCoords[3] = in_Texcoord3; - texCoords[4] = in_Texcoord4; - texCoords[5] = in_Texcoord5; - texCoords[6] = in_Texcoord6; - texCoords[7] = in_Texcoord7; - - vec4 transformedTexCoords[TextureStageCount]; - - for (uint i = 0; i < TextureStageCount; i++) { - // 0b111 = 7 - uint inputIndex = (texcoordIndices() >> (i * 3)) & 7; - uint inputFlags = (texcoordFlags() >> (i * 3)) & 7; - uint texcoordCount = (vertexTexcoordDeclMask() >> (inputIndex * 3)) & 7; - - vec4 transformed; - - uint flags = (transformFlags() >> (i * 3)) & 7; - - // Passing 0xffffffff results in it getting clamped to the dimensions of the texture coords and getting treated as PROJECTED - // but D3D9 does not apply the transformation matrix. - bool applyTransform = flags > D3DTTFF_COUNT1 && flags <= D3DTTFF_COUNT4; - - uint count = min(flags, 4u); - - // A projection component index of 4 means we won't do projection - uint projIndex = count != 0 ? count - 1 : 4; - - switch (inputFlags) { - default: - case (DXVK_TSS_TCI_PASSTHRU >> TCIOffset): - transformed = texCoords[inputIndex & 0xFF]; - - if (texcoordCount < 4) { - // Vulkan sets the w component to 1.0 if that's not provided by the vertex buffer, D3D9 expects 0 here - transformed.w = 0.0; - } - - if (applyTransform && !vertexHasPositionT()) { - /*This doesn't happen every time and I cannot figure out the difference between when it does and doesn't. - Keep it disabled for now, it's more likely that games rely on the zero texcoord than the weird 1 here. - if (texcoordCount <= 1) { - // y gets padded to 1 for some reason - transformed.y = 1.0; - }*/ - - if (texcoordCount >= 1 && texcoordCount < 4) { - // The first component after the last one thats backed by a vertex buffer gets padded to 1 for some reason. - uint idx = texcoordCount; - transformed[idx] = 1.0; - } - } else if (texcoordCount != 0 && !applyTransform) { - // COUNT0, COUNT1, COUNT > 4 => take count from vertex decl if that's not zero - count = texcoordCount; - } - - projIndex = count != 0 ? count - 1 : 4; - break; - - case (DXVK_TSS_TCI_CAMERASPACENORMAL >> TCIOffset): - transformed = outNrm; - if (!applyTransform) { - count = 3; - projIndex = 4; - } - break; - - case (DXVK_TSS_TCI_CAMERASPACEPOSITION >> TCIOffset): - transformed = vtx; - if (!applyTransform) { - count = 3; - projIndex = 4; - } - break; - - case (DXVK_TSS_TCI_CAMERASPACEREFLECTIONVECTOR >> TCIOffset): { - vec3 vtx3 = vtx.xyz; - vtx3 = normalize(vtx3); - - vec3 reflection = reflect(vtx3, normal); - transformed = vec4(reflection, 1.0); - if (!applyTransform) { - count = 3; - projIndex = 4; - } - break; - } - - case (DXVK_TSS_TCI_SPHEREMAP >> TCIOffset): { - vec3 vtx3 = vtx.xyz; - vtx3 = normalize(vtx3); - - vec3 reflection = reflect(vtx3, normal); - float m = length(reflection + vec3(0.0, 0.0, 1.0)) * 2.0; - - transformed = vec4( - reflection.x / m + 0.5, - reflection.y / m + 0.5, - 0.0, - 1.0 - ); - break; - } - } - - if (applyTransform && !vertexHasPositionT()) { - transformed = transformed * data.TexcoordMatrices[i]; - } - - // TODO: Shouldn't projected be checked per texture stage? - if (specUint(SpecSamplerProjected) != 0u && projIndex < 4) { - // The projection idx is always based on the flags, even when the input mode is not DXVK_TSS_TCI_PASSTHRU. - float projValue = transformed[projIndex]; - - // The w component is only used for projection or unused, so always insert the component that's supposed to be divided by there. - // The fragment shader will then decide whether to project or not. - transformed.w = projValue; - } - - // TODO: Shouldn't projected be checked per texture stage? - uint totalComponents = (specUint(SpecSamplerProjected) != 0u && projIndex < 4) ? 3 : 4; - for (uint j = count; j < totalComponents; j++) { - // Discard the components that exceed the specified D3DTTFF_COUNT - transformed[j] = 0.0; - } - - transformedTexCoords[i] = transformed; - } - - out_Texcoord0 = transformedTexCoords[0]; - out_Texcoord1 = transformedTexCoords[1]; - out_Texcoord2 = transformedTexCoords[2]; - out_Texcoord3 = transformedTexCoords[3]; - out_Texcoord4 = transformedTexCoords[4]; - out_Texcoord5 = transformedTexCoords[5]; - out_Texcoord6 = transformedTexCoords[6]; - out_Texcoord7 = transformedTexCoords[7]; - - if (useLighting()) { - vec4 diffuseValue = vec4(0.0); - vec4 specularValue = vec4(0.0); - vec4 ambientValue = vec4(0.0); - - for (uint i = 0; i < lightCount(); i++) { - D3D9Light light = data.Lights[i]; - - vec4 diffuse = light.Diffuse; - vec4 specular = light.Specular; - vec4 ambient = light.Ambient; - vec3 position = light.Position.xyz; - vec3 direction = light.Direction.xyz; - uint type = light.Type; - float range = light.Range; - float falloff = light.Falloff; - float atten0 = light.Attenuation0; - float atten1 = light.Attenuation1; - float atten2 = light.Attenuation2; - float theta = light.Theta; - float phi = light.Phi; - - bool isSpot = type == D3DLIGHT_SPOT; - bool isDirectional = type == D3DLIGHT_DIRECTIONAL; - - bvec3 isDirectional3 = bvec3(isDirectional); - - vec3 vtx3 = vtx.xyz; - - const bool useLegacyLights = specBool(SpecFFUseLegacyLights); - const bool isLegacyD3DLight2 = specBool(SpecFFIsLegacyD3DLight2); - - vec3 delta = position - vtx3; - float d = length(delta); - if (useLegacyLights && isLegacyD3DLight2) { - d = (range - d) / range; - } - vec3 hitDir = -direction; - hitDir = mix(delta, hitDir, isDirectional3); - hitDir = normalize(hitDir); - - float atten = fma(d, atten2, atten1); - atten = fma(d, atten, atten0); - if (!useLegacyLights) { - atten = 1.0 / atten; - } - atten = spvNMin(atten, FloatMaxValue); - - if (useLegacyLights && isLegacyD3DLight2) { - atten = d < 0.0 ? 0.0 : atten; // d > range - } else { - atten = d > range ? 0.0 : atten; - } - atten = isDirectional ? 1.0 : atten; - - // Spot Lighting - { - float rho = dot(-hitDir, direction); - float spotAtten = rho - phi; - spotAtten = spotAtten / (theta - phi); - spotAtten = pow(spotAtten, falloff); - - bool insideThetaAndPhi = rho <= theta; - bool insidePhi = rho > phi; - spotAtten = insidePhi ? spotAtten : 0.0; - spotAtten = insideThetaAndPhi ? spotAtten : 1.0; - spotAtten = clamp(spotAtten, 0.0, 1.0); - - spotAtten = atten * spotAtten; - atten = isSpot ? spotAtten : atten; - } - - float hitDot = dot(normal, hitDir); - hitDot = clamp(hitDot, 0.0, 1.0); - - float diffuseness = hitDot * atten; - - vec3 mid; - if (localViewer()) { - mid = normalize(vtx3); - mid = hitDir - mid; - } else { - mid = hitDir - vec3(0.0, 0.0, 1.0); - } - - mid = normalize(mid); - - float midDot = dot(normal, mid); - midDot = clamp(midDot, 0.0, 1.0); - bool doSpec = midDot > 0.0; - doSpec = doSpec && hitDot > 0.0; - if (useLegacyLights) { - doSpec = doSpec && data.Material.Power > 0.0; - } - - float specularness = pow(midDot, data.Material.Power); - specularness *= atten; - specularness = doSpec ? specularness : 0.0; - - vec4 lightAmbient = ambient * atten; - vec4 lightDiffuse = diffuse * diffuseness; - vec4 lightSpecular = specular * specularness; - - ambientValue += lightAmbient; - diffuseValue += lightDiffuse; - specularValue += lightSpecular; - } - - vec4 matDiffuse = pickMaterialSource(diffuseSource(), data.Material.Diffuse); - vec4 matAmbient = pickMaterialSource(ambientSource(), data.Material.Ambient); - vec4 matEmissive = pickMaterialSource(emissiveSource(), data.Material.Emissive); - vec4 matSpecular = pickMaterialSource(specularSource(), data.Material.Specular); - - vec4 finalColor0 = fma(matAmbient, data.GlobalAmbient, matEmissive); - finalColor0 = fma(matAmbient, ambientValue, finalColor0); - finalColor0 = fma(matDiffuse, diffuseValue, finalColor0); - finalColor0.a = matDiffuse.a; - - vec4 finalColor1 = matSpecular * specularValue; - - // Saturate - finalColor0 = clamp(finalColor0, vec4(0.0), vec4(1.0)); - - finalColor1 = clamp(finalColor1, vec4(0.0), vec4(1.0)); - - out_Color0 = finalColor0; - if (specularEnabled()) { - out_Color1 = finalColor1; - } else { - out_Color1 = vertexHasColor1() ? in_Color1 : vec4(0.0, 0.0, 0.0, 1.0); - // TODO: SM3 behavior, see below. - } - } else { - out_Color0 = vertexHasColor0() ? in_Color0 : vec4(1.0, 1.0, 1.0, 1.0); - out_Color1 = vertexHasColor1() ? in_Color1 : vec4(0.0, 0.0, 0.0, 1.0); - // TODO: If it's used with a SM3 PS, we need to export 0,0,0,0 as the default for color1. - // Implement that using a spec constant. - } - - out_Fog = calculateFog(vtx, vec4(0.0)); - - gl_PointSize = calculatePointSize(vtx); - - // We statically declare 6 clip planes, so we always need to write values. - emitVsClipping(vtx); -}