1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-02-27 04:54:15 +01:00

Merge branch 'master' into filter-uuid

This commit is contained in:
Philip Rebohle 2024-07-25 11:19:10 +02:00 committed by GitHub
commit 1e5555374b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
210 changed files with 9356 additions and 1927 deletions

View File

@ -9,13 +9,13 @@ jobs:
steps:
- name: Checkout code
id: checkout-code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup problem matcher
uses: Joshua-Ashton/gcc-problem-matcher@v2
uses: Joshua-Ashton/gcc-problem-matcher@v3
- name: Build release
id: build-release
@ -28,9 +28,9 @@ jobs:
- name: Upload artifacts
id: upload-artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: dxvk-${{ env.VERSION_NAME }}
name: dxvk-win-${{ env.VERSION_NAME }}
path: build/dxvk-${{ env.VERSION_NAME }}
if-no-files-found: error
@ -41,26 +41,43 @@ jobs:
steps:
- name: Checkout code
id: checkout-code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup problem matcher
uses: Joshua-Ashton/gcc-problem-matcher@v2
uses: Joshua-Ashton/gcc-problem-matcher@v3
- name: Build release
id: build-release
shell: bash
run: |
export VERSION_NAME="${GITHUB_REF##*/}-${GITHUB_SHA##*/}"
./package-native.sh ${VERSION_NAME} build --no-package
./package-native.sh ${VERSION_NAME} build
echo "VERSION_NAME=${VERSION_NAME}" >> $GITHUB_ENV
- name: Upload artifacts
id: upload-artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: dxvk-${{ env.VERSION_NAME }}
path: build/dxvk-native-${{ env.VERSION_NAME }}
name: dxvk-native-${{ env.VERSION_NAME }}
path: build/dxvk-native-${{ env.VERSION_NAME }}.tar.gz
if-no-files-found: error
merge-artifacts:
runs-on: ubuntu-20.04
needs: [artifacts-mingw-w64, artifacts-steamrt-sniper]
steps:
- name: Get version
id: get-version
shell: bash
run: |
echo "VERSION_NAME=${GITHUB_REF##*/}-${GITHUB_SHA##*/}" >> $GITHUB_ENV
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: dxvk-${{ env.VERSION_NAME }}
pattern: dxvk*
delete-merged: true

View File

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

View File

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

171
README.md
View File

@ -1,6 +1,6 @@
# DXVK
A Vulkan-based translation layer for Direct3D 9/10/11 which allows running 3D applications on Linux using Wine.
A Vulkan-based translation layer for Direct3D 8/9/10/11 which allows running 3D applications on Linux using Wine.
For the current status of the project, please refer to the [project wiki](https://github.com/doitsujin/dxvk/wiki).
@ -9,7 +9,7 @@ The most recent development builds can be found [here](https://github.com/doitsu
Release builds can be found [here](https://github.com/doitsujin/dxvk/releases).
## How to use
In order to install a DXVK package obtained from the [release](https://github.com/doitsujin/dxvk/releases) page into a given wine prefix, copy or symlink the DLLs into the following directories as follows, then open `winecfg` and manually add DLL overrides for `d3d11`, `d3d10core`, `dxgi`, and `d3d9`.
In order to install a DXVK package obtained from the [release](https://github.com/doitsujin/dxvk/releases) page into a given wine prefix, copy or symlink the DLLs into the following directories as follows, then open `winecfg` and manually add DLL overrides for `d3d8`, `d3d9`, `d3d10core`, `d3d11` and `dxgi`.
In a default Wine prefix that would be as follows:
```
@ -32,9 +32,84 @@ In order to remove DXVK from a prefix, remove the DLLs and DLL overrides, and ru
Tools such as Steam Play, Lutris, Bottles, Heroic Launcher, etc will automatically handle setup of dxvk on their own when enabled.
#### DLL dependencies
Listed below are the DLL requirements for using DXVK with any single API.
- d3d8: `d3d8.dll` and `d3d9.dll`
- d3d9: `d3d9.dll`
- d3d10: `d3d10core.dll`, `d3d11.dll` and `dxgi.dll`
- d3d11: `d3d11.dll` and `dxgi.dll`
### Notes on Vulkan drivers
Before reporting an issue, please check the [Wiki](https://github.com/doitsujin/dxvk/wiki/Driver-support) page on the current driver status and make sure you run a recent enough driver version for your hardware.
### Online multi-player games
Manipulation of Direct3D libraries in multi-player games may be considered cheating and can get your account **banned**. This may also apply to single-player games with an embedded or dedicated multiplayer portion. **Use at your own risk.**
### HUD
The `DXVK_HUD` environment variable controls a HUD which can display the framerate and some stat counters. It accepts a comma-separated list of the following options:
- `devinfo`: Displays the name of the GPU and the driver version.
- `fps`: Shows the current frame rate.
- `frametimes`: Shows a frame time graph.
- `submissions`: Shows the number of command buffers submitted per frame.
- `drawcalls`: Shows the number of draw calls and render passes per frame.
- `pipelines`: Shows the total number of graphics and compute pipelines.
- `descriptors`: Shows the number of descriptor pools and descriptor sets.
- `memory`: Shows the amount of device memory allocated and used.
- `gpuload`: Shows estimated GPU load. May be inaccurate.
- `version`: Shows DXVK version.
- `api`: Shows the D3D feature level used by the application.
- `cs`: Shows worker thread statistics.
- `compiler`: Shows shader compiler activity
- `samplers`: Shows the current number of sampler pairs used *[D3D9 Only]*
- `scale=x`: Scales the HUD by a factor of `x` (e.g. `1.5`)
- `opacity=y`: Adjusts the HUD opacity by a factor of `y` (e.g. `0.5`, `1.0` being fully opaque).
Additionally, `DXVK_HUD=1` has the same effect as `DXVK_HUD=devinfo,fps`, and `DXVK_HUD=full` enables all available HUD elements.
### Logs
When used with Wine, DXVK will print log messages to `stderr`. Additionally, standalone log files can optionally be generated by setting the `DXVK_LOG_PATH` variable, where log files in the given directory will be called `app_d3d11.log`, `app_dxgi.log` etc., where `app` is the name of the game executable.
On Windows, log files will be created in the game's working directory by default, which is usually next to the game executable.
### Frame rate limit
The `DXVK_FRAME_RATE` environment variable can be used to limit the frame rate. A value of `0` uncaps the frame rate, while any positive value will limit rendering to the given number of frames per second. Alternatively, the configuration file can be used.
### Device filter
Some applications do not provide a method to select a different GPU. In that case, DXVK can be forced to use a given device:
- `DXVK_FILTER_DEVICE_NAME="Device Name"` Selects devices with a matching Vulkan device name, which can be retrieved with tools such as `vulkaninfo`. Matches on substrings, so "VEGA" or "AMD RADV VEGA10" is supported if the full device name is "AMD RADV VEGA10 (LLVM 9.0.0)", for example. If the substring matches more than one device, the first device matched will be used.
**Note:** If the device filter is configured incorrectly, it may filter out all devices and applications will be unable to create a D3D device.
### Debugging
The following environment variables can be used for **debugging** purposes.
- `VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation` Enables Vulkan debug layers. Highly recommended for troubleshooting rendering issues and driver crashes. Requires the Vulkan SDK to be installed on the host system.
- `DXVK_LOG_LEVEL=none|error|warn|info|debug` Controls message logging.
- `DXVK_LOG_PATH=/some/directory` Changes path where log files are stored. Set to `none` to disable log file creation entirely, without disabling logging.
- `DXVK_DEBUG=markers|validation` Enables use of the `VK_EXT_debug_utils` extension for translating performance event markers, or to enable Vulkan validation, respecticely.
- `DXVK_CONFIG_FILE=/xxx/dxvk.conf` Sets path to the configuration file.
- `DXVK_CONFIG="dxgi.hideAmdGpu = True; dxgi.syncInterval = 0"` Can be used to set config variables through the environment instead of a configuration file using the same syntax. `;` is used as a seperator.
### Graphics Pipeline Library
On drivers which support `VK_EXT_graphics_pipeline_library` Vulkan shaders will be compiled at the time the game loads its D3D shaders, rather than at draw time. This reduces or eliminates shader compile stutter in many games when compared to the previous system.
In games that load their shaders during loading screens or in the menu, this can lead to prolonged periods of very high CPU utilization, especially on weaker CPUs. For affected games it is recommended to wait for shader compilation to finish before starting the game to avoid stutter and low performance. Shader compiler activity can be monitored with `DXVK_HUD=compiler`.
This feature largely replaces the state cache.
**Note:** Games which only load their D3D shaders at draw time (e.g. most Unreal Engine games) will still exhibit some stutter, although it should still be less severe than without this feature.
### State cache
DXVK caches pipeline state by default, so that shaders can be recompiled ahead of time on subsequent runs of an application, even if the driver's own shader cache got invalidated in the meantime. This cache is enabled by default, and generally reduces stuttering.
The following environment variables can be used to control the cache:
- `DXVK_STATE_CACHE`: Controls the state cache. The following values are supported:
- `disable`: Disables the cache entirely.
- `reset`: Clears the cache file.
- `DXVK_STATE_CACHE_PATH=/some/directory` Specifies a directory where to put the cache files. Defaults to the current working directory of the application.
This feature is mostly only relevant on systems without support for `VK_EXT_graphics_pipeline_library`
## Build instructions
In order to pull in all submodules that are needed for building, clone the repository using the following command:
@ -42,8 +117,6 @@ In order to pull in all submodules that are needed for building, clone the repos
git clone --recursive https://github.com/doitsujin/dxvk.git
```
### Requirements:
- [wine 7.1](https://www.winehq.org/) or newer
- [Meson](https://mesonbuild.com/) build system (at least version 0.49)
@ -76,76 +149,9 @@ cd build.w64
ninja install
```
The D3D9, D3D10, D3D11 and DXGI DLLs will be located in `/your/dxvk/directory/bin`. Setup has to be done manually in this case.
The D3D8, D3D9, D3D10, D3D11 and DXGI DLLs will be located in `/your/dxvk/directory/bin`.
### Online multi-player games
Manipulation of Direct3D libraries in multi-player games may be considered cheating and can get your account **banned**. This may also apply to single-player games with an embedded or dedicated multiplayer portion. **Use at your own risk.**
### Logs
When used with Wine, DXVK will print log messages to `stderr`. Additionally, standalone log files can optionally be generated by setting the `DXVK_LOG_PATH` variable, where log files in the given directory will be called `app_d3d11.log`, `app_dxgi.log` etc., where `app` is the name of the game executable.
On Windows, log files will be created in the game's working directory by default, which is usually next to the game executable.
### HUD
The `DXVK_HUD` environment variable controls a HUD which can display the framerate and some stat counters. It accepts a comma-separated list of the following options:
- `devinfo`: Displays the name of the GPU and the driver version.
- `fps`: Shows the current frame rate.
- `frametimes`: Shows a frame time graph.
- `submissions`: Shows the number of command buffers submitted per frame.
- `drawcalls`: Shows the number of draw calls and render passes per frame.
- `pipelines`: Shows the total number of graphics and compute pipelines.
- `descriptors`: Shows the number of descriptor pools and descriptor sets.
- `memory`: Shows the amount of device memory allocated and used.
- `gpuload`: Shows estimated GPU load. May be inaccurate.
- `version`: Shows DXVK version.
- `api`: Shows the D3D feature level used by the application.
- `cs`: Shows worker thread statistics.
- `compiler`: Shows shader compiler activity
- `samplers`: Shows the current number of sampler pairs used *[D3D9 Only]*
- `scale=x`: Scales the HUD by a factor of `x` (e.g. `1.5`)
- `opacity=y`: Adjusts the HUD opacity by a factor of `y` (e.g. `0.5`, `1.0` being fully opaque).
Additionally, `DXVK_HUD=1` has the same effect as `DXVK_HUD=devinfo,fps`, and `DXVK_HUD=full` enables all available HUD elements.
### Frame rate limit
The `DXVK_FRAME_RATE` environment variable can be used to limit the frame rate. A value of `0` uncaps the frame rate, while any positive value will limit rendering to the given number of frames per second. Alternatively, the configuration file can be used.
### Device filter
Some applications do not provide a method to select a different GPU. In that case, DXVK can be forced to use a given device:
- `DXVK_FILTER_DEVICE_NAME="Device Name"` Selects devices with a matching Vulkan device name, which can be retrieved with tools such as `vulkaninfo`. Matches on substrings, so "VEGA" or "AMD RADV VEGA10" is supported if the full device name is "AMD RADV VEGA10 (LLVM 9.0.0)", for example. If the substring matches more than one device, the first device matched will be used.
**Note:** If the device filter is configured incorrectly, it may filter out all devices and applications will be unable to create a D3D device.
### Graphics Pipeline Library
On drivers which support `VK_EXT_graphics_pipeline_library` Vulkan shaders will be compiled at the time the game loads its D3D shaders, rather than at draw time. This reduces or eliminates shader compile stutter in many games when compared to the previous system.
In games that load their shaders during loading screens or in the menu, this can lead to prolonged periods of very high CPU utilization, especially on weaker CPUs. For affected games it is recommended to wait for shader compilation to finish before starting the game to avoid stutter and low performance. Shader compiler activity can be monitored with `DXVK_HUD=compiler`.
This feature largely replaces the state cache.
**Note:** Games which only load their D3D shaders at draw time (e.g. most Unreal Engine games) will still exhibit some stutter, although it should still be less severe than without this feature.
### State cache
DXVK caches pipeline state by default, so that shaders can be recompiled ahead of time on subsequent runs of an application, even if the driver's own shader cache got invalidated in the meantime. This cache is enabled by default, and generally reduces stuttering.
The following environment variables can be used to control the cache:
- `DXVK_STATE_CACHE`: Controls the state cache. The following values are supported:
- `disable`: Disables the cache entirely.
- `reset`: Clears the cache file.
- `DXVK_STATE_CACHE_PATH=/some/directory` Specifies a directory where to put the cache files. Defaults to the current working directory of the application.
This feature is mostly only relevant on systems without support for `VK_EXT_graphics_pipeline_library`
### Debugging
The following environment variables can be used for **debugging** purposes.
- `VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation` Enables Vulkan debug layers. Highly recommended for troubleshooting rendering issues and driver crashes. Requires the Vulkan SDK to be installed on the host system.
- `DXVK_LOG_LEVEL=none|error|warn|info|debug` Controls message logging.
- `DXVK_LOG_PATH=/some/directory` Changes path where log files are stored. Set to `none` to disable log file creation entirely, without disabling logging.
- `DXVK_DEBUG=markers|validation` Enables use of the `VK_EXT_debug_utils` extension for translating performance event markers, or to enable Vulkan validation, respecticely.
- `DXVK_CONFIG_FILE=/xxx/dxvk.conf` Sets path to the configuration file.
- `DXVK_CONFIG="dxgi.hideAmdGpu = True; dxgi.syncInterval = 0"` Can be used to set config variables through the environment instead of a configuration file using the same syntax. `;` is used as a seperator.
## Troubleshooting
### Build troubleshooting
DXVK requires threading support from your mingw-w64 build environment. If you
are missing this, you may see "error: std::cv_status has not been declared"
or similar threading related errors.
@ -163,3 +169,22 @@ For non debian based distros, make sure that your mingw-w64-gcc cross compiler
does have `--enable-threads=posix` enabled during configure. If your distro does
ship its mingw-w64-gcc binary with `--enable-threads=win32` you might have to
recompile locally or open a bug at your distro's bugtracker to ask for it.
# DXVK Native
DXVK Native is a version of DXVK which allows it to be used natively without Wine.
This is primarily useful for game and application ports to either avoid having to write another rendering backend, or to help with port bringup during development.
[Release builds](https://github.com/doitsujin/dxvk/releases) are built using the Steam Runtime.
### How does it work?
DXVK Native replaces certain Windows-isms with a platform and framework-agnostic replacement, for example, `HWND`s can become `SDL_Window*`s, etc.
All it takes to do that is to add another WSI backend.
**Note:** DXVK Native requires a backend to be explicitly set via the `DXVK_WSI_DRIVER` environment variable. The current built-in options are `SDL2` and `GLFW`.
DXVK Native comes with a slim set of Windows header definitions required for D3D9/11 and the MinGW headers for D3D9/11.
In most cases, it will end up being plug and play with your renderer, but there may be certain teething issues such as:
- `__uuidof(type)` is supported, but `__uuidof(variable)` is not supported. Use `__uuidof_var(variable)` instead.

View File

@ -1 +1 @@
2.2
2.4

View File

@ -1,3 +1,10 @@
# Device filter. Only exposes devices whose Vulkan device name contains
# the given string. May be useful to force an application to run on a
# specific GPU, but not applications launched by that application.
# dxvk.deviceFilter = ""
# Expose the HDR10 ColorSpace (DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
# to the application by default.
# This shows to the game that the global Windows 'HDR Mode' is enabled.
@ -10,6 +17,7 @@
# dxgi.enableHDR = True
# Create the VkSurface on the first call to IDXGISwapChain::Present,
# rather than when creating the swap chain. Some games that start
# rendering with a different graphics API may require this option,
@ -35,7 +43,12 @@
# bugs in games that have physics or other simulation tied to their frame
# rate, but do not provide their own limiter.
#
# Supported values : Any non-negative integer
# Supported values
# -1: Always disables the limiter
# 0: Default behaviour. Limits the frame rate to the selected display
# refresh rate when vertical synchronization is enabled if the
# actual display mode does not match the game's one.
# n: Limit to n frames per second.
# dxgi.maxFrameRate = 0
# d3d9.maxFrameRate = 0
@ -62,7 +75,7 @@
# Report Nvidia GPUs as AMD GPUs. Unless NVAPI support is explicitly
# enabled through proton, this is done by default in order to work
# enabled through Proton, this is done by default in order to work
# around crashes or low performance with Nvidia-speciic code paths
# in games, especially Unreal Engine.
#
@ -71,6 +84,13 @@
# dxgi.hideNvidiaGpu = Auto
# Report Nvidia GPUs running on NVK as AMD GPUs.
#
# Supported values: Auto, True, False
# dxgi.hideNvkGpu = Auto
# Report AMD GPUs as Nvidia GPUs. This is only done for games that are
# known to have issues with AMDAGS or other AMD-specific code paths.
#
@ -90,6 +110,7 @@
# Override maximum amount of device memory and shared system memory
# reported to the application. This may fix texture streaming issues
# in games that do not support cards with large amounts of VRAM.
# This is not a hard cap and applications can choose to ignore it.
#
# Supported values: Any number in Megabytes.
@ -292,6 +313,32 @@
# d3d11.enableContextLock = False
# Exposes or hides support for driver command lists
#
# Some games use the feature flag to decide whether to use deferred
# contexts or not. We enable this by default, but in some situations
# this can lead to issues if games detect an AMD GPU where command
# lists are not natively supported on Windows.
#
# Supported values: True, False
# d3d11.exposeDriverCommandLists = True
# Reproducible Command Stream
#
# Ensure that for the same D3D commands the output VK commands
# don't change between runs. Useful for comparative benchmarking,
# can negatively affect performance and can break some games
# that don't use queries correctly.
#
# Supported values:
# - True/False
# d3d11.reproducibleCommandStream = False
# d3d9.reproducibleCommandStream = False
# Sets number of pipeline compiler threads.
#
# If the graphics pipeline library feature is enabled, the given
@ -479,6 +526,7 @@
# Supported values:
# - True/False
# d3d11.longMad = False
# d3d9.longMad = False
# Device Local Constant Buffers
@ -494,7 +542,9 @@
# Support DF formats
#
# Support the vendor extension DF floating point depth formats
# Support the vendor extension DF floating point depth formats on AMD and Intel.
# Note that this config is ignored and disabled by default on Nvidia, or when
# spoofing a Nvidia GPU, as it does not support these formats natively.
#
# Supported values:
# - True/False
@ -520,14 +570,16 @@
# d3d9.supportX4R4G4B4 = True
# Support D32
# Support D16_LOCKABLE
#
# Support the D32 format.
# Support the D16_LOCKABLE format.
# Always enabled on AMD, or when spoofing an AMD GPU
# via customVendorId, disabled by default on Nvidia and Intel.
#
# Supported values:
# - True/False
# d3d9.supportD32 = True
# d3d9.supportD16Lockable = False
# Disable A8 as a Render Target
#
@ -623,3 +675,37 @@
# DO NOT CHANGE THIS UNLESS YOU HAVE A VERY GOOD REASON.
# d3d9.textureMemory = 100
# Hide integrated graphics from applications
#
# Only has an effect when dedicated GPUs are present on the system. It is
# not recommended to use this option at all unless absolutely necessary for
# a game to work; prefer using DXVK_FILTER_DEVICE_NAME whenever possible.
#
# Supported values:
# - True/False
# dxvk.hideIntegratedGraphics = False
# Trigger DEVICELOST when losing focus
#
# D3D9 requires the application to call Device::Reset after
# it loses focus in fullscreen.
# Some games rely on observing a D3DERR_DEVICELOST or D3DERR_NOTRESET.
# Others don't handle it correctly.
#
# Supported values:
# - True/False
# d3d9.deviceLossOnFocusLoss = False
# Reject Device::Reset if any losable resource is still alive
#
# D3D9 rejects Device::Reset if there's still any alive resources of specific types.
# (State blocks, additional swapchains, D3DPOOL_DEFAULT resources)
# Some games leak resources leading to a hang.
#
# Supported values:
# - True/False
# d3d9.countLosableResources = True

View File

@ -0,0 +1,19 @@
install_subdir(
'directx',
install_dir: get_option('includedir') / 'dxvk',
strip_directory: true,
exclude_files: '.git'
)
install_subdir(
'windows',
install_dir: get_option('includedir') / 'dxvk',
strip_directory: true,
)
install_headers(
'wsi/native_wsi.h',
'wsi/native_sdl2.h',
'wsi/native_glfw.h',
subdir: 'dxvk/wsi',
)

View File

@ -48,7 +48,6 @@ typedef const void* LPCVOID;
typedef size_t SIZE_T;
typedef int8_t INT8;
typedef uint8_t UINT8;
typedef uint8_t BYTE;
@ -56,9 +55,13 @@ typedef int16_t SHORT;
typedef uint16_t USHORT;
typedef int64_t LONGLONG;
typedef int64_t INT64;
typedef uint64_t ULONGLONG;
typedef uint64_t UINT64;
typedef intptr_t LONG_PTR;
typedef uintptr_t ULONG_PTR;
typedef float FLOAT;
@ -330,12 +333,21 @@ typedef struct RGNDATA {
#define DECLARE_INTERFACE(x) struct x
#define DECLARE_INTERFACE_(x, y) struct x : public y
#else
#ifdef CONST_VTABLE
#define DECLARE_INTERFACE(x) \
typedef interface x { \
const struct x##Vtbl *lpVtbl; \
} x; \
typedef const struct x##Vtbl x##Vtbl; \
const struct x##Vtbl
#else
#define DECLARE_INTERFACE(x) \
typedef interface x { \
struct x##Vtbl *lpVtbl; \
} x; \
typedef struct x##Vtbl x##Vtbl; \
struct x##Vtbl
#endif // CONST_VTABLE
#define DECLARE_INTERFACE_(x, y) DECLARE_INTERFACE(x)
#endif // __cplusplus

View File

@ -1,6 +1,6 @@
#include <windows.h>
#include <SDL2/SDL.h>
#include <SDL.h>
namespace dxvk::wsi {

@ -1 +1 @@
Subproject commit 0bcc624926a25a2a273d07877fd25a6ff5ba1cfb
Subproject commit 8b246ff75c6615ba4532fe4fde20f1be090c3764

@ -1 +1 @@
Subproject commit 85c2334e92e215cce34e8e0ed8b2dce4700f4a50
Subproject commit 46dc0f6e514f5730784bb2cac2a7c731636839e8

View File

@ -1,11 +1,13 @@
project('dxvk', ['c', 'cpp'], version : 'v2.2', meson_version : '>= 0.49', default_options : [ 'cpp_std=c++17', 'warning_level=2' ])
project('dxvk', ['c', 'cpp'], version : 'v2.4.0', meson_version : '>= 0.58', default_options : [ 'cpp_std=c++17', 'warning_level=2' ])
pkg = import('pkgconfig')
cpu_family = target_machine.cpu_family()
platform = target_machine.system()
fs = import('fs')
cpp = meson.get_compiler('cpp')
cc = meson.get_compiler('c')
dxvk_is_msvc = cpp.get_id() == 'msvc'
dxvk_is_msvc = cpp.get_argument_syntax() == 'msvc'
compiler_args = [
'-msse',
@ -33,42 +35,60 @@ if get_option('build_id')
]
endif
dxvk_include_dirs = [
'./include',
'./include/vulkan/include',
'./include/spirv/include'
]
dxvk_include_dirs = ['./include']
if fs.is_dir('./include/vulkan/include')
dxvk_include_dirs += ['./include/vulkan/include']
elif not cpp.check_header('vulkan/vulkan.h')
error('Missing Vulkan-Headers')
endif
if fs.is_dir('./include/spirv/include')
dxvk_include_dirs += ['./include/spirv/include']
elif not cpp.check_header('spirv/unified1/spirv.hpp')
error('Missing SPIRV-Headers')
endif
proj_displayinfo = subproject('libdisplay-info')
dep_displayinfo = proj_displayinfo.get_variable('di_dep')
dep_displayinfo = dependency(
'libdisplay-info',
version: ['>= 0.0.0', '< 0.2.0'],
fallback: ['libdisplay-info', 'di_dep'],
default_options: ['default_library=static'],
)
if platform == 'windows'
dxvk_so_version = {'name_prefix': ''}
compiler_args += [
'-DNOMINMAX',
'-D_WIN32_WINNT=0xa00',
]
link_args += [
'-static',
'-static-libgcc',
'-static-libstdc++',
# We need to set the section alignment for debug symbols to
# work properly as well as avoiding a memcpy from the Wine loader.
'-Wl,--file-alignment=4096',
]
# Wine's built-in back traces only work with dwarf4 symbols
if get_option('debug')
compiler_args += [
'-gdwarf-4',
]
endif
# Enable stdcall fixup on 32-bit
if cpu_family == 'x86'
if not dxvk_is_msvc
link_args += [
'-Wl,--enable-stdcall-fixup',
'-Wl,--kill-at',
'-static',
'-static-libgcc',
'-static-libstdc++',
# We need to set the section alignment for debug symbols to
# work properly as well as avoiding a memcpy from the Wine loader.
'-Wl,--file-alignment=4096',
]
# Wine's built-in back traces only work with dwarf4 symbols
if get_option('debug')
compiler_args += [
'-gdwarf-4',
]
endif
# Enable stdcall fixup on 32-bit
if cpu_family == 'x86'
link_args += [
'-Wl,--enable-stdcall-fixup',
'-Wl,--kill-at',
]
endif
else
link_args += [
'/FILEALIGN:4096',
]
endif
@ -98,10 +118,21 @@ if platform == 'windows'
)
endif
dxvk_wsi = 'win32'
dxvk_name_prefix = ''
compiler_args += ['-DDXVK_WSI_WIN32']
else
dxvk_abi_version = '0'
dxvk_version_raw = meson.project_version().strip('v').split('.')
dxvk_version = [ dxvk_version_raw[0] ]
foreach i : [ 1, 2 ]
padded = dxvk_version_raw[i]
if padded.to_int() < 10
padded = '0' + padded
endif
dxvk_version += [ padded ]
endforeach
dxvk_so_version = {'version': dxvk_abi_version + '.' + dxvk_version[0] + dxvk_version[1] + dxvk_version[2]}
wrc = find_program('touch')
wrc_generator = generator(wrc, output : [ '@BASENAME@_ignored.h' ], arguments : [ '@OUTPUT@' ] )
@ -111,17 +142,20 @@ else
'./include/native/directx'
]
dxvk_wsi = get_option('dxvk_native_wsi')
if dxvk_wsi == 'sdl2'
lib_sdl2 = cpp.find_library('SDL2')
lib_sdl2 = dependency('SDL2', required: false)
lib_glfw = dependency('glfw', required: false)
if lib_sdl2.found()
compiler_args += ['-DDXVK_WSI_SDL2']
elif dxvk_wsi == 'glfw'
lib_glfw = cpp.find_library('glfw')
endif
if lib_glfw.found()
compiler_args += ['-DDXVK_WSI_GLFW']
endif
if (not lib_sdl2.found() and not lib_glfw.found())
error('SDL2 or GLFW are required to build dxvk-native')
endif
dxvk_name_prefix = 'libdxvk_'
dxvk_name_prefix = 'dxvk_'
dxvk_pkg_prefix = 'dxvk-'
link_args += [
'-static-libgcc',
@ -137,13 +171,12 @@ add_project_link_arguments(cpp.get_supported_link_arguments(link_args), language
add_project_link_arguments(cc.get_supported_link_arguments(link_args), language: 'c')
exe_ext = ''
dll_ext = ''
def_spec_ext = '.def'
glsl_compiler = find_program('glslang', 'glslangValidator')
glsl_args = [
'--quiet',
'--target-env', 'vulkan1.2',
'--target-env', 'vulkan1.3',
'--vn', '@BASENAME@',
'--depfile', '@DEPFILE@',
'@INPUT@',
@ -162,4 +195,8 @@ dxvk_version = vcs_tag(
output: 'version.h',
)
if platform != 'windows'
subdir('include/native')
endif
subdir('src')

View File

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

View File

@ -25,6 +25,8 @@ shift 2
opt_nopackage=0
opt_devbuild=0
opt_buildid=false
opt_64_only=0
opt_32_only=0
CC=${CC:="gcc"}
CXX=${CXX:="g++"}
@ -41,6 +43,12 @@ while [ $# -gt 0 ]; do
"--build-id")
opt_buildid=true
;;
"--64-only")
opt_64_only=1
;;
"--32-only")
opt_32_only=1
;;
*)
echo "Unrecognized option: $1" >&2
exit 1
@ -56,13 +64,14 @@ function build_arch {
opt_strip=--strip
fi
CC="$CC -m$1" CXX="$CXX -m$1" meson setup \
--buildtype "release" \
--prefix "$DXVK_BUILD_DIR/usr" \
$opt_strip \
--bindir "$2" \
--libdir "$2" \
-Dbuild_id=$opt_buildid \
CC="$CC -m$1" CXX="$CXX -m$1" meson setup \
--buildtype "release" \
--prefix "$DXVK_BUILD_DIR/usr" \
$opt_strip \
--bindir "$2" \
--libdir "$2" \
-Dbuild_id=$opt_buildid \
--force-fallback-for=libdisplay-info \
"$DXVK_BUILD_DIR/build.$1"
cd "$DXVK_BUILD_DIR/build.$1"
@ -80,8 +89,12 @@ function package {
rm -R "dxvk-native-$DXVK_VERSION"
}
build_arch 64 lib
build_arch 32 lib32
if [ $opt_32_only -eq 0 ]; then
build_arch 64 lib
fi
if [ $opt_64_only -eq 0 ]; then
build_arch 32 lib32
fi
if [ $opt_nopackage -eq 0 ]; then
package

View File

@ -15,16 +15,23 @@ else
d3d10_d3d11_dep = d3d11_dep
endif
d3d10_core_dll = shared_library('d3d10core'+dll_ext, d3d10_core_src, d3d10_core_res,
name_prefix : dxvk_name_prefix,
d3d10_core_dll = shared_library(dxvk_name_prefix+'d3d10core', d3d10_core_src, d3d10_core_res,
dependencies : [ d3d10_d3d11_dep ],
include_directories : dxvk_include_path,
install : true,
vs_module_defs : 'd3d10core'+def_spec_ext,
link_args : d3d10_core_ld_args,
link_depends : [ d3d10_core_link_depends ],
kwargs : dxvk_so_version,
)
d3d10_core_dep = declare_dependency(
link_with : [ d3d10_core_dll ],
)
if platform != 'windows'
pkg.generate(d3d10_core_dll,
filebase: dxvk_pkg_prefix + 'd3d10core',
subdirs: 'dxvk',
)
endif

View File

@ -12,7 +12,7 @@ namespace dxvk {
const D3D11_ON_12_RESOURCE_INFO* p11on12Info)
: D3D11DeviceChild<ID3D11Buffer>(pDevice),
m_desc (*pDesc),
m_resource (this),
m_resource (this, pDevice),
m_d3d10 (this) {
DxvkBufferCreateInfo info;
info.flags = 0;

View File

@ -386,11 +386,16 @@ namespace dxvk {
const UINT Values[4]) {
D3D10DeviceLock lock = LockContext();
auto uav = static_cast<D3D11UnorderedAccessView*>(pUnorderedAccessView);
if (!uav)
if (!pUnorderedAccessView)
return;
Com<ID3D11UnorderedAccessView> qiUav;
if (FAILED(pUnorderedAccessView->QueryInterface(IID_PPV_ARGS(&qiUav))))
return;
auto uav = static_cast<D3D11UnorderedAccessView*>(qiUav.ptr());
// Gather UAV format info. We'll use this to determine
// whether we need to create a temporary view or not.
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
@ -923,6 +928,34 @@ namespace dxvk {
const void* pSrcData,
UINT SrcRowPitch,
UINT SrcDepthPitch) {
if (IsDeferred && unlikely(pDstBox != nullptr) && unlikely(!m_parent->GetOptions()->exposeDriverCommandLists)) {
// If called from a deferred context and native command list support is not
// exposed, we need to apply the destination box to the source pointer. This
// only applies to UpdateSubresource, not to UpdateSubresource1. See MSDN:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ff476486(v=vs.85).aspx)
size_t srcOffset = pDstBox->left;
// For textures, the offset logic needs to take the format into account.
// Ignore that multi-planar images exist, this is hairy enough already.
D3D11CommonTexture* dstTexture = GetCommonTexture(pDstResource);
if (dstTexture) {
auto dstFormat = dstTexture->GetPackedFormat();
auto dstFormatInfo = lookupFormatInfo(dstFormat);
size_t blockSize = dstFormatInfo->elementSize;
VkOffset3D offset;
offset.x = pDstBox->left / dstFormatInfo->blockSize.width;
offset.y = pDstBox->top / dstFormatInfo->blockSize.height;
offset.z = pDstBox->front / dstFormatInfo->blockSize.depth;
srcOffset = offset.x * blockSize + offset.y * SrcRowPitch + offset.z * SrcDepthPitch;
}
pSrcData = reinterpret_cast<const char*>(pSrcData) + srcOffset;
}
UpdateResource(pDstResource, DstSubresource, pDstBox,
pSrcData, SrcRowPitch, SrcDepthPitch, 0);
}

View File

@ -1130,7 +1130,7 @@ namespace dxvk {
if (likely(pBuffer != nullptr))
bufferSize = static_cast<D3D11Buffer*>(pBuffer)->Desc()->ByteWidth;
return bufferSize >= Offset + Size;
return uint64_t(bufferSize) >= uint64_t(Offset) + uint64_t(Size);
}
private:

View File

@ -19,6 +19,7 @@ namespace dxvk {
m_csThread(Device, Device->createContext(DxvkContextType::Primary)),
m_maxImplicitDiscardSize(pParent->GetOptions()->maxImplicitDiscardSize),
m_submissionFence(new sync::CallbackFence()),
m_flushTracker(pParent->GetOptions()->reproducibleCommandStream),
m_multithread(this, false, pParent->GetOptions()->enableContextLock),
m_videoContext(this, Device) {
EmitCs([

View File

@ -89,6 +89,10 @@ namespace dxvk {
void SynchronizeCsThread(
uint64_t SequenceNumber);
D3D10Multithread& GetMultithread() {
return m_multithread;
}
D3D10DeviceLock LockContext() {
return m_multithread.AcquireLock();
}

View File

@ -45,10 +45,10 @@ namespace dxvk {
m_dxvkDevice (pContainer->GetDXVKDevice()),
m_dxvkAdapter (m_dxvkDevice->adapter()),
m_d3d11Formats (m_dxvkDevice),
m_d3d11Options (m_dxvkDevice->instance()->config(), m_dxvkDevice),
m_d3d11Options (m_dxvkDevice->instance()->config()),
m_dxbcOptions (m_dxvkDevice, m_d3d11Options),
m_maxFeatureLevel (GetMaxFeatureLevel(m_dxvkDevice->instance(), m_dxvkDevice->adapter())),
m_deviceFeatures (m_dxvkDevice->instance(), m_dxvkDevice->adapter(), m_featureLevel) {
m_deviceFeatures (m_dxvkDevice->instance(), m_dxvkDevice->adapter(), m_d3d11Options, m_featureLevel) {
m_initializer = new D3D11Initializer(this);
m_context = new D3D11ImmediateContext(this, m_dxvkDevice);
m_d3d10Device = new D3D10Device(this, m_context.ptr());
@ -1348,7 +1348,7 @@ namespace dxvk {
m_deviceFeatures = D3D11DeviceFeatures(
m_dxvkDevice->instance(),
m_dxvkDevice->adapter(),
m_featureLevel);
m_d3d11Options, m_featureLevel);
}
if (pChosenFeatureLevel)
@ -3411,8 +3411,9 @@ namespace dxvk {
HRESULT STDMETHODCALLTYPE D3D11DXGIDevice::EnqueueSetEvent(HANDLE hEvent) {
Logger::err("D3D11DXGIDevice::EnqueueSetEvent: Not implemented");
return DXGI_ERROR_UNSUPPORTED;
auto immediateContext = m_d3d11Device.GetContext();
immediateContext->Flush1(D3D11_CONTEXT_TYPE_ALL, hEvent);
return S_OK;
}

View File

@ -12,6 +12,7 @@ namespace dxvk {
D3D11DeviceFeatures::D3D11DeviceFeatures(
const Rc<DxvkInstance>& Instance,
const Rc<DxvkAdapter>& Adapter,
const D3D11Options& Options,
D3D_FEATURE_LEVEL FeatureLevel)
: m_features (Adapter->features()),
m_properties (Adapter->devicePropertiesExt()) {
@ -107,7 +108,7 @@ namespace dxvk {
m_gpuVirtualAddress.MaxGPUVirtualAddressBitsPerProcess = 40;
// Marker support only depends on the debug utils extension
m_marker.Profile = Instance->extensions().extDebugUtils;
m_marker.Profile = static_cast<bool>(Instance->extensions().extDebugUtils);
// DXVK will keep all shaders in memory once created, and all Vulkan
// drivers that we know of that can run DXVK have an on-disk cache.
@ -118,11 +119,11 @@ namespace dxvk {
m_shaderMinPrecision.PixelShaderMinPrecision = 0;
m_shaderMinPrecision.AllOtherShaderStagesMinPrecision = 0;
// Report native support for command lists here so that we do not actually have
// to re-implement the UpdateSubresource bug from the D3D11 runtime, see MSDN:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ff476486(v=vs.85).aspx)
// Report native support for command lists by default. Deferred context
// usage can be beneficial for us as ExecuteCommandList has low overhead,
// and we avoid having to deal with known UpdateSubresource bugs this way.
m_threading.DriverConcurrentCreates = TRUE;
m_threading.DriverCommandLists = TRUE;
m_threading.DriverCommandLists = Options.exposeDriverCommandLists;
}
@ -182,7 +183,8 @@ namespace dxvk {
D3D_FEATURE_LEVEL D3D11DeviceFeatures::GetMaxFeatureLevel(
const Rc<DxvkInstance>& Instance,
const Rc<DxvkAdapter>& Adapter) {
D3D11DeviceFeatures features(Instance, Adapter, D3D_FEATURE_LEVEL_12_1);
D3D11Options options(Instance->config());
D3D11DeviceFeatures features(Instance, Adapter, options, D3D_FEATURE_LEVEL_12_1);
return features.GetMaxFeatureLevel();
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "d3d11_include.h"
#include "d3d11_options.h"
#include "../dxvk/dxvk_adapter.h"
#include "../dxvk/dxvk_instance.h"
@ -21,6 +22,7 @@ namespace dxvk {
D3D11DeviceFeatures(
const Rc<DxvkInstance>& Instance,
const Rc<DxvkAdapter>& Adapter,
const D3D11Options& Options,
D3D_FEATURE_LEVEL FeatureLevel);
~D3D11DeviceFeatures();

View File

@ -183,13 +183,7 @@ ID3D11VkExtContext1 : public ID3D11VkExtContext {
};
#ifdef _MSC_VER
struct __declspec(uuid("bb8a4fb9-3935-4762-b44b-35189a26414a")) ID3D11VkExtShader;
struct __declspec(uuid("8a6e3c42-f74c-45b7-8265-a231b677ca17")) ID3D11VkExtDevice;
struct __declspec(uuid("cfcf64ef-9586-46d0-bca4-97cf2ca61b06")) ID3D11VkExtDevice1;
struct __declspec(uuid("fd0bca13-5cb6-4c3a-987e-4750de2ca791")) ID3D11VkExtContext;
struct __declspec(uuid("874b09b2-ae0b-41d8-8476-5f3b7a0e879d")) ID3D11VkExtContext1;
#else
#ifndef _MSC_VER
__CRT_UUID_DECL(ID3D11VkExtShader, 0xbb8a4fb9,0x3935,0x4762,0xb4,0x4b,0x35,0x18,0x9a,0x26,0x41,0x4a);
__CRT_UUID_DECL(ID3D11VkExtDevice, 0x8a6e3c42,0xf74c,0x45b7,0x82,0x65,0xa2,0x31,0xb6,0x77,0xca,0x17);
__CRT_UUID_DECL(ID3D11VkExtDevice1, 0xcfcf64ef,0x9586,0x46d0,0xbc,0xa4,0x97,0xcf,0x2c,0xa6,0x1b,0x06);

View File

@ -38,7 +38,7 @@ extern "C" {
DXGI_ADAPTER_DESC desc;
pAdapter->GetDesc(&desc);
dxvkInstance = new DxvkInstance();
dxvkInstance = new DxvkInstance(0);
dxvkAdapter = dxvkInstance->findAdapterByLuid(&desc.AdapterLuid);
if (dxvkAdapter == nullptr)
@ -376,7 +376,7 @@ extern "C" {
instanceInfo.extensionCount = instanceExtensions.size();
instanceInfo.extensionNames = instanceExtensions.data();
Rc<DxvkInstance> dxvkInstance = new DxvkInstance(instanceInfo);
Rc<DxvkInstance> dxvkInstance = new DxvkInstance(instanceInfo, 0);
// Find adapter by physical device handle
Rc<DxvkAdapter> dxvkAdapter;

View File

@ -49,8 +49,6 @@ ID3D12DXVKInteropDevice : public IUnknown {
};
#ifdef _MSC_VER
struct __declspec(uuid("39da4e09-bd1c-4198-9fae-86bbe3be41fd")) ID3D12DXVKInteropDevice;
#else
#ifndef _MSC_VER
__CRT_UUID_DECL(ID3D12DXVKInteropDevice, 0x39da4e09, 0xbd1c, 0x4198, 0x9f,0xae, 0x86,0xbb,0xe3,0xbe,0x41,0xfd)
#endif

View File

@ -12,7 +12,7 @@ namespace dxvk {
#endif
}
D3D11Options::D3D11Options(const Config& config, const Rc<DxvkDevice>& device) {
D3D11Options::D3D11Options(const Config& config) {
this->dcSingleUseMode = config.getOption<bool>("d3d11.dcSingleUseMode", true);
this->zeroInitWorkgroupMemory = config.getOption<bool>("d3d11.zeroInitWorkgroupMemory", false);
this->forceVolatileTgsmAccess = config.getOption<bool>("d3d11.forceVolatileTgsmAccess", false);
@ -30,8 +30,9 @@ namespace dxvk {
this->deferSurfaceCreation = config.getOption<bool>("dxgi.deferSurfaceCreation", false);
this->numBackBuffers = config.getOption<int32_t>("dxgi.numBackBuffers", 0);
this->maxFrameLatency = config.getOption<int32_t>("dxgi.maxFrameLatency", 0);
this->maxFrameRate = config.getOption<int32_t>("dxgi.maxFrameRate", 0);
this->syncInterval = config.getOption<int32_t>("dxgi.syncInterval", -1);
this->exposeDriverCommandLists = config.getOption<bool>("d3d11.exposeDriverCommandLists", true);
this->longMad = config.getOption<bool>("d3d11.longMad", false);
this->reproducibleCommandStream = config.getOption<bool>("d3d11.reproducibleCommandStream", false);
// Clamp LOD bias so that people don't abuse this in unintended ways
this->samplerLodBias = dxvk::fclamp(this->samplerLodBias, -2.0f, 1.0f);

View File

@ -11,7 +11,7 @@
namespace dxvk {
struct D3D11Options {
D3D11Options(const Config& config, const Rc<DxvkDevice>& device);
D3D11Options(const Config& config);
/// Enables speed hack for mapping on deferred contexts
///
@ -76,17 +76,10 @@ namespace dxvk {
/// Overrides DXGI_SWAP_CHAIN_DESC::BufferCount.
int32_t numBackBuffers;
/// Sync interval. Overrides the value
/// passed to IDXGISwapChain::Present.
int32_t syncInterval;
/// Override maximum frame latency if the app specifies
/// a higher value. May help with frame timing issues.
int32_t maxFrameLatency;
/// Limit frame rate
int32_t maxFrameRate;
/// Limit discardable resource size
VkDeviceSize maxImplicitDiscardSize;
@ -117,8 +110,21 @@ namespace dxvk {
/// race conditions.
bool enableContextLock;
/// Whether to expose the driver command list feature. Enabled by
/// default and generally beneficial, but some games may assume that
/// this is not supported when running on an AMD GPU.
bool exposeDriverCommandLists;
/// Shader dump path
std::string shaderDumpPath;
/// Should we make our Mads a FFma or do it the long way with an FMul and an FAdd?
bool longMad;
/// Ensure that for the same D3D commands the output VK commands
/// don't change between runs. Useful for comparative benchmarking,
/// can negatively affect performance.
bool reproducibleCommandStream;
};
}

View File

@ -9,11 +9,10 @@
namespace dxvk {
D3D11DXGIKeyedMutex::D3D11DXGIKeyedMutex(
ID3D11Resource* pResource)
: m_resource(pResource) {
Com<ID3D11Device> device;
m_resource->GetDevice(&device);
m_device = static_cast<D3D11Device*>(device.ptr());
ID3D11Resource* pResource,
D3D11Device* pDevice)
: m_resource(pResource),
m_device(pDevice) {
m_supported = m_device->GetDXVKDevice()->features().khrWin32KeyedMutex
&& m_device->GetDXVKDevice()->vkd()->wine_vkAcquireKeyedMutex != nullptr
@ -112,7 +111,17 @@ namespace dxvk {
D3D11CommonTexture* texture = GetCommonTexture(m_resource);
Rc<DxvkDevice> dxvkDevice = m_device->GetDXVKDevice();
m_device->GetContext()->WaitForResource(texture->GetImage(), DxvkCsThread::SynchronizeAll, D3D11_MAP_READ_WRITE, 0);
{
D3D11ImmediateContext* context = m_device->GetContext();
D3D10Multithread& multithread = context->GetMultithread();
static bool s_errorShown = false;
if (!multithread.GetMultithreadProtected() && !std::exchange(s_errorShown, true))
Logger::warn("D3D11DXGIKeyedMutex::ReleaseSync: Called without context locking enabled.");
D3D10DeviceLock lock = context->LockContext();
context->WaitForResource(texture->GetImage(), DxvkCsThread::SynchronizeAll, D3D11_MAP_READ_WRITE, 0);
}
return dxvkDevice->vkd()->wine_vkReleaseKeyedMutex(dxvkDevice->handle(), texture->GetImage()->memory().memory(), Key) == VK_SUCCESS
? S_OK
@ -120,9 +129,10 @@ namespace dxvk {
}
D3D11DXGIResource::D3D11DXGIResource(
ID3D11Resource* pResource)
ID3D11Resource* pResource,
D3D11Device* pDevice)
: m_resource(pResource),
m_keyedMutex(pResource) {
m_keyedMutex(pResource, pDevice) {
}

View File

@ -30,7 +30,8 @@ namespace dxvk {
public:
D3D11DXGIKeyedMutex(
ID3D11Resource* pResource);
ID3D11Resource* pResource,
D3D11Device* pDevice);
~D3D11DXGIKeyedMutex();
@ -88,7 +89,8 @@ namespace dxvk {
public:
D3D11DXGIResource(
ID3D11Resource* pResource);
ID3D11Resource* pResource,
D3D11Device* pDevice);
~D3D11DXGIResource();

View File

@ -99,7 +99,8 @@ namespace dxvk {
if (riid == __uuidof(IUnknown)
|| riid == __uuidof(IDXGIVkSwapChain)
|| riid == __uuidof(IDXGIVkSwapChain1)) {
|| riid == __uuidof(IDXGIVkSwapChain1)
|| riid == __uuidof(IDXGIVkSwapChain2)) {
*ppvObject = ref(this);
return S_OK;
}
@ -254,11 +255,6 @@ namespace dxvk {
UINT SyncInterval,
UINT PresentFlags,
const DXGI_PRESENT_PARAMETERS* pPresentParameters) {
auto options = m_parent->GetOptions();
if (options->syncInterval >= 0)
SyncInterval = options->syncInterval;
if (!(PresentFlags & DXGI_PRESENT_TEST))
m_dirty |= m_presenter->setSyncInterval(SyncInterval) != VK_SUCCESS;
@ -352,6 +348,15 @@ namespace dxvk {
}
void STDMETHODCALLTYPE D3D11SwapChain::SetTargetFrameRate(
double FrameRate) {
m_targetFrameRate = FrameRate;
if (m_presenter != nullptr)
m_presenter->setFrameRateLimit(m_targetFrameRate);
}
HRESULT D3D11SwapChain::PresentImage(UINT SyncInterval) {
// Flush pending rendering commands before
auto immediateContext = m_parent->GetContext();
@ -501,7 +506,7 @@ namespace dxvk {
presenterDesc.fullScreenExclusive = PickFullscreenMode();
m_presenter = new Presenter(m_device, m_frameLatencySignal, presenterDesc);
m_presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate);
m_presenter->setFrameRateLimit(m_targetFrameRate);
}

View File

@ -13,7 +13,7 @@ namespace dxvk {
class D3D11Device;
class D3D11DXGIDevice;
class D3D11SwapChain : public ComObject<IDXGIVkSwapChain1> {
class D3D11SwapChain : public ComObject<IDXGIVkSwapChain2> {
constexpr static uint32_t DefaultFrameLatency = 1;
public:
@ -86,6 +86,9 @@ namespace dxvk {
void STDMETHODCALLTYPE GetFrameStatistics(
DXGI_VK_FRAME_STATISTICS* pFrameStatistics);
void STDMETHODCALLTYPE SetTargetFrameRate(
double FrameRate);
private:
enum BindingIds : uint32_t {
@ -116,18 +119,20 @@ namespace dxvk {
std::vector<Rc<DxvkImageView>> m_imageViews;
uint64_t m_frameId = DXGI_MAX_SWAP_CHAIN_BUFFERS;
uint32_t m_frameLatency = DefaultFrameLatency;
uint32_t m_frameLatencyCap = 0;
HANDLE m_frameLatencyEvent = nullptr;
Rc<sync::CallbackFence> m_frameLatencySignal;
uint64_t m_frameId = DXGI_MAX_SWAP_CHAIN_BUFFERS;
uint32_t m_frameLatency = DefaultFrameLatency;
uint32_t m_frameLatencyCap = 0;
HANDLE m_frameLatencyEvent = nullptr;
Rc<sync::CallbackFence> m_frameLatencySignal;
bool m_dirty = true;
bool m_dirty = true;
VkColorSpaceKHR m_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
VkColorSpaceKHR m_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
std::optional<VkHdrMetadataEXT> m_hdrMetadata;
bool m_dirtyHdrMetadata = true;
bool m_dirtyHdrMetadata = true;
double m_targetFrameRate = 0.0;
dxvk::mutex m_frameStatisticsLock;
DXGI_VK_FRAME_STATISTICS m_frameStatistics = { };

View File

@ -58,9 +58,6 @@ namespace dxvk {
"\n MiscFlags: ", m_desc.MiscFlags,
"\n FeatureLevel: ", pDevice->GetFeatureLevel()));
if (m_desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX)
Logger::warn("D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX: not supported.");
imageInfo.shared = true;
imageInfo.sharing.mode = hSharedHandle == INVALID_HANDLE_VALUE ? DxvkSharedHandleMode::Export : DxvkSharedHandleMode::Import;
imageInfo.sharing.type = (m_desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_NTHANDLE)
@ -214,8 +211,11 @@ namespace dxvk {
// For some formats, we need to enable sampled and/or
// render target capabilities if available, but these
// should in no way affect the default image layout
imageInfo.usage |= EnableMetaCopyUsage(imageInfo.format, imageInfo.tiling);
imageInfo.usage |= EnableMetaPackUsage(imageInfo.format, m_desc.CPUAccessFlags);
imageInfo.usage |= EnableMetaCopyUsage(imageInfo.format, imageInfo.tiling);
for (uint32_t i = 0; i < imageInfo.viewFormatCount; i++)
imageInfo.usage |= EnableMetaCopyUsage(imageInfo.viewFormats[i], imageInfo.tiling);
// Check if we can actually create the image
if (!CheckImageSupport(&imageInfo, imageInfo.tiling)) {
@ -1099,7 +1099,7 @@ namespace dxvk {
m_texture (this, pDevice, pDesc, p11on12Info, D3D11_RESOURCE_DIMENSION_TEXTURE1D, 0, VK_NULL_HANDLE, nullptr),
m_interop (this, &m_texture),
m_surface (this, &m_texture),
m_resource(this),
m_resource(this, pDevice),
m_d3d10 (this) {
}
@ -1205,7 +1205,7 @@ namespace dxvk {
m_texture (this, pDevice, pDesc, p11on12Info, D3D11_RESOURCE_DIMENSION_TEXTURE2D, 0, VK_NULL_HANDLE, hSharedHandle),
m_interop (this, &m_texture),
m_surface (this, &m_texture),
m_resource (this),
m_resource (this, pDevice),
m_d3d10 (this),
m_swapChain (nullptr) {
}
@ -1220,7 +1220,7 @@ namespace dxvk {
m_texture (this, pDevice, pDesc, nullptr, D3D11_RESOURCE_DIMENSION_TEXTURE2D, DxgiUsage, vkImage, nullptr),
m_interop (this, &m_texture),
m_surface (this, &m_texture),
m_resource (this),
m_resource (this, pDevice),
m_d3d10 (this),
m_swapChain (nullptr) {
@ -1236,7 +1236,7 @@ namespace dxvk {
m_texture (this, pDevice, pDesc, nullptr, D3D11_RESOURCE_DIMENSION_TEXTURE2D, DxgiUsage, VK_NULL_HANDLE, nullptr),
m_interop (this, &m_texture),
m_surface (this, &m_texture),
m_resource (this),
m_resource (this, pDevice),
m_d3d10 (this),
m_swapChain (pSwapChain) {
@ -1384,7 +1384,7 @@ namespace dxvk {
: D3D11DeviceChild<ID3D11Texture3D1>(pDevice),
m_texture (this, pDevice, pDesc, p11on12Info, D3D11_RESOURCE_DIMENSION_TEXTURE3D, 0, VK_NULL_HANDLE, nullptr),
m_interop (this, &m_texture),
m_resource(this),
m_resource(this, pDevice),
m_d3d10 (this) {
}

View File

@ -1262,12 +1262,28 @@ namespace dxvk {
viewport.height = float(cStreamState.dstRect.bottom) - viewport.y;
}
VkExtent3D viewExtent = cViews[0]->mipLevelExtent(0);
VkRect2D srcRect;
srcRect.offset = { 0, 0 };
srcRect.extent = { viewExtent.width, viewExtent.height };
if (cStreamState.srcRectEnabled) {
srcRect.offset.x = cStreamState.srcRect.left;
srcRect.offset.y = cStreamState.srcRect.top;
srcRect.extent.width = cStreamState.srcRect.right - srcRect.offset.x;
srcRect.extent.height = cStreamState.srcRect.bottom - srcRect.offset.y;
}
UboData uboData = { };
uboData.colorMatrix[0][0] = 1.0f;
uboData.colorMatrix[1][1] = 1.0f;
uboData.colorMatrix[2][2] = 1.0f;
uboData.coordMatrix[0][0] = 1.0f;
uboData.coordMatrix[1][1] = 1.0f;
uboData.coordMatrix[0][0] = float(srcRect.extent.width) / float(viewExtent.width);
uboData.coordMatrix[1][1] = float(srcRect.extent.height) / float(viewExtent.height);
uboData.coordMatrix[2][0] = float(srcRect.offset.x) / float(viewExtent.width);
uboData.coordMatrix[2][1] = float(srcRect.offset.y) / float(viewExtent.height);
uboData.srcRect = srcRect;
uboData.yMin = 0.0f;
uboData.yMax = 1.0f;
uboData.isPlanar = cViews[1] != nullptr;
@ -1290,17 +1306,14 @@ namespace dxvk {
ctx->bindShader<VK_SHADER_STAGE_FRAGMENT_BIT>(Rc<DxvkShader>(m_fs));
ctx->bindUniformBuffer(VK_SHADER_STAGE_FRAGMENT_BIT, 0, DxvkBufferSlice(m_ubo));
ctx->bindResourceSampler(VK_SHADER_STAGE_FRAGMENT_BIT, 1, Rc<DxvkSampler>(m_sampler));
for (uint32_t i = 0; i < cViews.size(); i++)
ctx->bindResourceImageView(VK_SHADER_STAGE_FRAGMENT_BIT, 2 + i, Rc<DxvkImageView>(cViews[i]));
ctx->bindResourceImageView(VK_SHADER_STAGE_FRAGMENT_BIT, 1 + i, Rc<DxvkImageView>(cViews[i]));
ctx->draw(3, 1, 0, 0);
ctx->bindResourceSampler(VK_SHADER_STAGE_FRAGMENT_BIT, 1, nullptr);
for (uint32_t i = 0; i < cViews.size(); i++)
ctx->bindResourceImageView(VK_SHADER_STAGE_FRAGMENT_BIT, 2 + i, nullptr);
ctx->bindResourceImageView(VK_SHADER_STAGE_FRAGMENT_BIT, 1 + i, nullptr);
});
}
@ -1315,38 +1328,14 @@ namespace dxvk {
}
void D3D11VideoContext::CreateSampler() {
DxvkSamplerCreateInfo samplerInfo;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
samplerInfo.mipmapLodBias = 0.0f;
samplerInfo.mipmapLodMin = 0.0f;
samplerInfo.mipmapLodMax = 0.0f;
samplerInfo.useAnisotropy = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0f;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.compareToDepth = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.reductionMode = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE;
samplerInfo.borderColor = VkClearColorValue();
samplerInfo.usePixelCoord = VK_FALSE;
samplerInfo.nonSeamless = VK_FALSE;
m_sampler = m_device->createSampler(samplerInfo);
}
void D3D11VideoContext::CreateShaders() {
SpirvCodeBuffer vsCode(d3d11_video_blit_vert);
SpirvCodeBuffer fsCode(d3d11_video_blit_frag);
const std::array<DxvkBindingInfo, 4> fsBindings = {{
const std::array<DxvkBindingInfo, 3> fsBindings = {{
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, VK_IMAGE_VIEW_TYPE_MAX_ENUM, VK_SHADER_STAGE_FRAGMENT_BIT, VK_ACCESS_UNIFORM_READ_BIT, VK_TRUE },
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_IMAGE_VIEW_TYPE_MAX_ENUM, VK_SHADER_STAGE_FRAGMENT_BIT, 0 },
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_IMAGE_VIEW_TYPE_2D, VK_SHADER_STAGE_FRAGMENT_BIT, VK_ACCESS_SHADER_READ_BIT },
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 2, VK_IMAGE_VIEW_TYPE_2D, VK_SHADER_STAGE_FRAGMENT_BIT, VK_ACCESS_SHADER_READ_BIT },
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 3, VK_IMAGE_VIEW_TYPE_2D, VK_SHADER_STAGE_FRAGMENT_BIT, VK_ACCESS_SHADER_READ_BIT },
}};
DxvkShaderCreateInfo vsInfo;
@ -1368,7 +1357,6 @@ namespace dxvk {
if (std::exchange(m_resourcesCreated, true))
return;
CreateSampler();
CreateUniformBuffer();
CreateShaders();
}

View File

@ -584,6 +584,7 @@ namespace dxvk {
struct alignas(16) UboData {
float colorMatrix[3][4];
float coordMatrix[3][2];
VkRect2D srcRect;
float yMin, yMax;
VkBool32 isPlanar;
};
@ -593,7 +594,6 @@ namespace dxvk {
Rc<DxvkDevice> m_device;
Rc<DxvkShader> m_vs;
Rc<DxvkShader> m_fs;
Rc<DxvkSampler> m_sampler;
Rc<DxvkBuffer> m_ubo;
VkExtent2D m_dstExtent = { 0u, 0u };
@ -613,8 +613,6 @@ namespace dxvk {
void CreateUniformBuffer();
void CreateSampler();
void CreateShaders();
void CreateResources();

View File

@ -298,7 +298,7 @@ namespace dxvk {
D3D11_BUFFER_DESC bufferDesc;
static_cast<D3D11Buffer*>(pResource)->GetDesc(&bufferDesc);
if (bufferDesc.MiscFlags == D3D11_RESOURCE_MISC_BUFFER_STRUCTURED) {
if (bufferDesc.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_STRUCTURED) {
pDesc->Format = DXGI_FORMAT_UNKNOWN;
pDesc->ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
pDesc->Buffer.FirstElement = 0;

View File

@ -214,7 +214,7 @@ namespace dxvk {
D3D11_BUFFER_DESC bufferDesc;
static_cast<D3D11Buffer*>(pResource)->GetDesc(&bufferDesc);
if (bufferDesc.MiscFlags == D3D11_RESOURCE_MISC_BUFFER_STRUCTURED) {
if (bufferDesc.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_STRUCTURED) {
pDesc->Format = DXGI_FORMAT_UNKNOWN;
pDesc->ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
pDesc->Buffer.FirstElement = 0;

View File

@ -77,18 +77,25 @@ else
d3d11_dxgi_dep = dxgi_dep
endif
d3d11_dll = shared_library('d3d11'+dll_ext, dxgi_common_src + d3d11_src + d3d10_src,
d3d11_dll = shared_library(dxvk_name_prefix+'d3d11', dxgi_common_src + d3d11_src + d3d10_src,
glsl_generator.process(d3d11_shaders), d3d11_res,
name_prefix : dxvk_name_prefix,
dependencies : [ d3d11_dxgi_dep, dxbc_dep, dxvk_dep ],
include_directories : dxvk_include_path,
install : true,
vs_module_defs : 'd3d11'+def_spec_ext,
link_args : d3d11_ld_args,
link_depends : [ d3d11_link_depends ],
kwargs : dxvk_so_version,
)
d3d11_dep = declare_dependency(
link_with : [ d3d11_dll ],
include_directories : [ dxvk_include_path ],
)
if platform != 'windows'
pkg.generate(d3d11_dll,
filebase: dxvk_pkg_prefix + 'd3d11',
subdirs: 'dxvk',
)
endif

View File

@ -1,5 +1,7 @@
#version 450
#extension GL_EXT_samplerless_texture_functions : require
// Can't use matrix types here since even a two-row
// matrix will be padded to 16 bytes per column for
// absolutely no reason
@ -11,6 +13,8 @@ uniform ubo_t {
vec2 coord_matrix_c1;
vec2 coord_matrix_c2;
vec2 coord_matrix_c3;
uvec2 src_offset;
uvec2 src_extent;
float y_min;
float y_max;
bool is_planar;
@ -19,9 +23,8 @@ uniform ubo_t {
layout(location = 0) in vec2 i_texcoord;
layout(location = 0) out vec4 o_color;
layout(set = 0, binding = 1) uniform sampler s_sampler;
layout(set = 0, binding = 2) uniform texture2D s_inputY;
layout(set = 0, binding = 3) uniform texture2D s_inputCbCr;
layout(set = 0, binding = 1) uniform texture2D s_inputY;
layout(set = 0, binding = 2) uniform texture2D s_inputCbCr;
void main() {
// Transform input texture coordinates to
@ -31,25 +34,61 @@ void main() {
coord_matrix_c2,
coord_matrix_c3);
vec2 coord = coord_matrix * vec3(i_texcoord, 1.0f);
// Fetch source image color
vec4 color = vec4(0.0f, 0.0f, 0.0f, 1.0f);
if (is_planar) {
color.g = texture(sampler2D(s_inputY, s_sampler), coord).r;
color.rb = texture(sampler2D(s_inputCbCr, s_sampler), coord).gr;
color.g = clamp((color.g - y_min) / (y_max - y_min), 0.0f, 1.0f);
} else {
color = texture(sampler2D(s_inputY, s_sampler), coord);
}
// Color space transformation
// Load color space transform
mat3x4 color_matrix = mat3x4(
color_matrix_r1,
color_matrix_r2,
color_matrix_r3);
o_color.rgb = vec4(color.rgb, 1.0f) * color_matrix;
o_color.a = color.a;
// Compute actual pixel coordinates to sample. We filter
// manually in order to avoid bleeding from pixels outside
// the source rectangle.
vec2 abs_size_y = vec2(textureSize(s_inputY, 0));
vec2 abs_size_c = vec2(textureSize(s_inputCbCr, 0));
vec2 coord = coord_matrix * vec3(i_texcoord, 1.0f);
coord -= 0.5f / abs_size_y;
vec2 size_factor = abs_size_c / abs_size_y;
vec2 src_lo = vec2(src_offset);
vec2 src_hi = vec2(src_offset + src_extent - 1u);
vec2 abs_coord = coord * abs_size_y;
vec2 fract_coord = fract(clamp(abs_coord, src_lo, src_hi));
vec4 accum = vec4(0.0f, 0.0f, 0.0f, 0.0f);
for (int i = 0; i < 4; i++) {
ivec2 offset = ivec2(i & 1, i >> 1);
// Compute exact pixel coordinates for the current
// iteration and clamp it to the source rectangle.
vec2 fetch_coord = clamp(abs_coord + vec2(offset), src_lo, src_hi);
// Fetch actual pixel color in source color space
vec4 color;
if (is_planar) {
color.g = texelFetch(s_inputY, ivec2(fetch_coord), 0).r;
color.rb = texelFetch(s_inputCbCr, ivec2(fetch_coord * size_factor), 0).gr;
color.g = clamp((color.g - y_min) / (y_max - y_min), 0.0f, 1.0f);
color.a = 1.0f;
} else {
color = texelFetch(s_inputY, ivec2(fetch_coord), 0);
}
// Transform color space before accumulation
color.rgb = vec4(color.rgb, 1.0f) * color_matrix;
// Filter and accumulate final pixel color
vec2 factor = fract_coord;
if (offset.x == 0) factor.x = 1.0f - factor.x;
if (offset.y == 0) factor.y = 1.0f - factor.y;
accum += factor.x * factor.y * color;
}
o_color = accum;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

View File

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

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

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

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

View File

@ -162,11 +162,18 @@ namespace dxvk {
if (mapping.FormatSrgb == VK_FORMAT_UNDEFINED && srgb)
return D3DERR_NOTAVAILABLE;
if (RType == D3DRTYPE_CUBETEXTURE && mapping.Aspect != VK_IMAGE_ASPECT_COLOR_BIT)
return D3DERR_NOTAVAILABLE;
if (RType == D3DRTYPE_VERTEXBUFFER || RType == D3DRTYPE_INDEXBUFFER)
return D3D_OK;
// Let's actually ask Vulkan now that we got some quirks out the way!
return CheckDeviceVkFormat(mapping.FormatColor, Usage, RType);
VkFormat format = mapping.FormatColor;
if (unlikely(mapping.ConversionFormatInfo.FormatColor != VK_FORMAT_UNDEFINED)) {
format = mapping.ConversionFormatInfo.FormatColor;
}
return CheckDeviceVkFormat(format, Usage, RType);
}
@ -224,11 +231,15 @@ namespace dxvk {
if (!IsDepthFormat(DepthStencilFormat))
return D3DERR_NOTAVAILABLE;
auto dsfMapping = GetFormatMapping(DepthStencilFormat);
if (dsfMapping.FormatColor == VK_FORMAT_UNDEFINED)
return D3DERR_NOTAVAILABLE;
if (RenderTargetFormat == dxvk::D3D9Format::NULL_FORMAT)
return D3D_OK;
auto mapping = ConvertFormatUnfixed(RenderTargetFormat);
if (mapping.FormatColor == VK_FORMAT_UNDEFINED)
auto rtfMapping = GetFormatMapping(RenderTargetFormat);
if (rtfMapping.FormatColor == VK_FORMAT_UNDEFINED)
return D3DERR_NOTAVAILABLE;
return D3D_OK;
@ -240,7 +251,8 @@ namespace dxvk {
D3D9Format SourceFormat,
D3D9Format TargetFormat) {
bool sourceSupported = SourceFormat != D3D9Format::Unknown
&& IsSupportedBackBufferFormat(SourceFormat);
&& (IsSupportedBackBufferFormat(SourceFormat)
|| (IsFourCCFormat(SourceFormat) && !IsVendorFormat(SourceFormat)));
bool targetSupported = TargetFormat == D3D9Format::X1R5G5B5
|| TargetFormat == D3D9Format::A1R5G5B5
|| TargetFormat == D3D9Format::R5G6B5
@ -271,6 +283,8 @@ namespace dxvk {
auto& options = m_parent->GetOptions();
const VkPhysicalDeviceLimits& limits = m_adapter->deviceProperties().limits;
// TODO: Actually care about what the adapter supports here.
// ^ For Intel and older cards most likely here.
@ -531,7 +545,7 @@ namespace dxvk {
// Max Vertex Blend Matrix Index
pCaps->MaxVertexBlendMatrixIndex = 0;
// Max Point Size
pCaps->MaxPointSize = 256.0f;
pCaps->MaxPointSize = limits.pointSizeRange[1];
// Max Primitive Count
pCaps->MaxPrimitiveCount = 0x00555555;
// Max Vertex Index
@ -788,7 +802,8 @@ namespace dxvk {
// Fix up the D3DFORMAT to match what we are enumerating
mode.Format = static_cast<D3DFORMAT>(Format);
m_modes.push_back(mode);
if (std::count(m_modes.begin(), m_modes.end(), mode) == 0)
m_modes.push_back(mode);
}
// Sort display modes by width, height and refresh rate,

View File

@ -32,6 +32,10 @@ namespace dxvk {
m_device->m_implicitSwapchain->SetApiName(name);
}
void DxvkD3D8Bridge::SetD3D8CompatibilityMode(const bool compatMode) {
m_device->SetD3D8CompatibilityMode(compatMode);
}
HRESULT DxvkD3D8Bridge::UpdateTextureFromBuffer(
IDirect3DSurface9* pDestSurface,
IDirect3DSurface9* pSrcSurface,

View File

@ -3,8 +3,6 @@
#include <windows.h>
#include "../util/config/config.h"
#include "../vulkan/vulkan_loader.h"
/**
* The D3D9 bridge allows D3D8 to access DXVK internals.
* For Vulkan interop without needing DXVK internals, see d3d9_interop.h.
@ -30,6 +28,13 @@ IDxvkD3D8Bridge : public IUnknown {
*/
virtual void SetAPIName(const char* name) = 0;
/**
* \brief Enables or disables D3D9-specific device features and validations
*
* \param [in] compatibility state
*/
virtual void SetD3D8CompatibilityMode(const bool compatMode) = 0;
/**
* \brief Updates a D3D9 surface from a D3D9 buffer
*
@ -58,10 +63,7 @@ IDxvkD3D8InterfaceBridge : public IUnknown {
virtual const dxvk::Config* GetConfig() const = 0;
};
#if defined(_MSC_VER)
struct DECLSPEC_UUID("D3D9D3D8-42A9-4C1E-AA97-BEEFCAFE2000") IDxvkD3D8Bridge;
struct DECLSPEC_UUID("D3D9D3D8-A407-773E-18E9-CAFEBEEF3000") IDxvkD3D8InterfaceBridge;
#else
#ifndef _MSC_VER
__CRT_UUID_DECL(IDxvkD3D8Bridge, 0xD3D9D3D8, 0x42A9, 0x4C1E, 0xAA, 0x97, 0xBE, 0xEF, 0xCA, 0xFE, 0x20, 0x00);
__CRT_UUID_DECL(IDxvkD3D8InterfaceBridge, 0xD3D9D3D8, 0xA407, 0x773E, 0x18, 0xE9, 0xCA, 0xFE, 0xBE, 0xEF, 0x30, 0x00);
#endif
@ -83,6 +85,7 @@ namespace dxvk {
void** ppvObject);
void SetAPIName(const char* name);
void SetD3D8CompatibilityMode(const bool compatMode);
HRESULT UpdateTextureFromBuffer(
IDirect3DSurface9* pDestSurface,

View File

@ -125,7 +125,12 @@ namespace dxvk {
template <D3D9_COMMON_BUFFER_TYPE Type>
inline DxvkBufferSlice GetBufferSlice(VkDeviceSize offset, VkDeviceSize length) const {
return DxvkBufferSlice(GetBuffer<Type>(), offset, length);
if (likely(length && offset < m_desc.Size)) {
return DxvkBufferSlice(GetBuffer<Type>(), offset,
std::min<VkDeviceSize>(m_desc.Size - offset, length));
}
return DxvkBufferSlice();
}
inline DxvkBufferSliceHandle AllocMapSlice() {
@ -206,6 +211,10 @@ namespace dxvk {
: DxvkCsThread::SynchronizeAll;
}
bool IsSysmemDynamic() const {
return m_desc.Pool == D3DPOOL_SYSTEMMEM && (m_desc.Usage & D3DUSAGE_DYNAMIC) != 0;
}
private:
Rc<DxvkBuffer> CreateBuffer() const;

View File

@ -31,11 +31,12 @@ namespace dxvk {
AddDirtyBox(nullptr, i);
}
if (m_desc.Pool != D3DPOOL_DEFAULT) {
if (m_desc.Pool != D3DPOOL_DEFAULT && pSharedHandle) {
throw DxvkError("D3D9: Incompatible pool type for texture sharing.");
}
if (IsPoolManaged(m_desc.Pool)) {
SetAllNeedUpload();
if (pSharedHandle) {
throw DxvkError("D3D9: Incompatible pool type for texture sharing.");
}
}
m_mapping = pDevice->LookupFormat(m_desc.Format);
@ -117,6 +118,7 @@ namespace dxvk {
HRESULT D3D9CommonTexture::NormalizeTextureProperties(
D3D9DeviceEx* pDevice,
D3DRESOURCETYPE ResourceType,
D3D9_COMMON_TEXTURE_DESC* pDesc) {
auto* options = pDevice->GetOptions();
@ -130,6 +132,11 @@ namespace dxvk {
options->disableA8RT)
return D3DERR_INVALIDCALL;
// Cube textures with depth formats are not supported on any native
// driver, and allowing them triggers a broken code path in Gothic 3.
if (ResourceType == D3DRTYPE_CUBETEXTURE && mapping.Aspect != VK_IMAGE_ASPECT_COLOR_BIT)
return D3DERR_INVALIDCALL;
// If the mapping is invalid then lets return invalid
// Some edge cases:
// NULL format does not map to anything, but should succeed

View File

@ -179,11 +179,14 @@ namespace dxvk {
* Fills in undefined values and validates the texture
* parameters. Any error returned by this method should
* be forwarded to the application.
* \param [in] pDevice D3D9 device
* \param [in] ResourceType Resource type
* \param [in,out] pDesc Texture description
* \returns \c S_OK if the parameters are valid
*/
static HRESULT NormalizeTextureProperties(
D3D9DeviceEx* pDevice,
D3DRESOURCETYPE ResourceType,
D3D9_COMMON_TEXTURE_DESC* pDesc);
/**

View File

@ -13,7 +13,7 @@ namespace dxvk {
DxsoProgramType ShaderStage,
DxsoConstantBuffers BufferType,
VkDeviceSize Size)
: D3D9ConstantBuffer(pDevice, getBufferUsage(pDevice, ShaderStage, BufferType), GetShaderStage(ShaderStage),
: D3D9ConstantBuffer(pDevice, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, GetShaderStage(ShaderStage),
computeResourceSlotId(ShaderStage, DxsoBindingType::ConstantBuffer, BufferType),
Size) {
@ -135,21 +135,4 @@ namespace dxvk {
device->properties().extRobustness2.robustUniformBufferAccessSizeAlignment);
}
VkBufferUsageFlags D3D9ConstantBuffer::getBufferUsage(
D3D9DeviceEx* pDevice,
DxsoProgramType ShaderStage,
DxsoConstantBuffers BufferType) {
VkBufferUsageFlags result = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
// We won't always need this, but the only buffer that
// this applies to is so large that it will not matter.
if (pDevice->CanSWVP()
&& ShaderStage == DxsoProgramType::VertexShader
&& BufferType == DxsoConstantBuffers::VSConstantBuffer)
result |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
return result;
}
}

View File

@ -80,11 +80,6 @@ namespace dxvk {
VkDeviceSize getAlignment(const Rc<DxvkDevice>& device) const;
static VkBufferUsageFlags getBufferUsage(
D3D9DeviceEx* pDevice,
DxsoProgramType ShaderStage,
DxsoConstantBuffers BufferType);
};
}

View File

@ -26,14 +26,14 @@ namespace dxvk {
HRESULT D3D9Cursor::SetHardwareCursor(UINT XHotSpot, UINT YHotSpot, const CursorBitmap& bitmap) {
DWORD mask[32];
CursorMask mask;
std::memset(mask, ~0, sizeof(mask));
ICONINFO info;
info.fIcon = FALSE;
info.xHotspot = XHotSpot;
info.yHotspot = YHotSpot;
info.hbmMask = ::CreateBitmap(HardwareCursorWidth, HardwareCursorHeight, 1, 1, mask);
info.hbmMask = ::CreateBitmap(HardwareCursorWidth, HardwareCursorHeight, 1, 1, &mask[0]);
info.hbmColor = ::CreateBitmap(HardwareCursorWidth, HardwareCursorHeight, 1, 32, &bitmap[0]);
if (m_hCursor != nullptr)

View File

@ -11,11 +11,20 @@ namespace dxvk {
// Format Size of 4 bytes (ARGB)
using CursorBitmap = uint8_t[HardwareCursorHeight * HardwareCursorPitch];
// Monochrome mask (1 bit)
using CursorMask = uint8_t[HardwareCursorHeight * HardwareCursorWidth / 8];
class D3D9Cursor {
public:
#ifdef _WIN32
~D3D9Cursor() {
if (m_hCursor != nullptr)
::DestroyCursor(m_hCursor);
}
#endif
void UpdateCursor(int X, int Y);
BOOL ShowCursor(BOOL bShow);

View File

@ -56,6 +56,7 @@ namespace dxvk {
, m_csThread ( dxvkDevice, dxvkDevice->createContext(DxvkContextType::Primary) )
, m_csChunk ( AllocCsChunk() )
, m_submissionFence (new sync::Fence())
, m_flushTracker (m_d3d9Options.reproducibleCommandStream)
, m_d3d9Interop ( this )
, m_d3d9On12 ( this )
, m_d3d8Bridge ( this ) {
@ -455,6 +456,8 @@ namespace dxvk {
SetDepthStencilSurface(nullptr);
}
m_flags.clr(D3D9DeviceFlag::InScene);
/*
* Before calling the IDirect3DDevice9::Reset method for a device,
* an application should release any explicit render targets,
@ -464,7 +467,7 @@ namespace dxvk {
* We have to check after ResetState clears the references held by SetTexture, etc.
* This matches what Windows D3D9 does.
*/
if (unlikely(m_losableResourceCounter.load() != 0 && !IsExtended())) {
if (unlikely(m_losableResourceCounter.load() != 0 && !IsExtended() && m_d3d9Options.countLosableResources)) {
Logger::warn(str::format("Device reset failed because device still has alive losable resources: Device not reset. Remaining resources: ", m_losableResourceCounter.load()));
m_deviceLostState = D3D9DeviceLostState::NotReset;
return D3DERR_INVALIDCALL;
@ -479,9 +482,20 @@ namespace dxvk {
return hr;
}
// Unbind all buffers that were still bound to the backend to avoid leaks.
EmitCs([](DxvkContext* ctx) {
ctx->bindIndexBuffer(DxvkBufferSlice(), VK_INDEX_TYPE_UINT32);
for (uint32_t i = 0; i < caps::MaxStreams; i++) {
ctx->bindVertexBuffer(i, DxvkBufferSlice(), 0);
}
});
Flush();
SynchronizeCsThread(DxvkCsThread::SynchronizeAll);
if (m_d3d9Options.deferSurfaceCreation)
m_deviceHasBeenReset = true;
return D3D_OK;
}
@ -581,7 +595,7 @@ namespace dxvk {
|| (Usage & D3DUSAGE_DYNAMIC)
|| IsVendorFormat(EnumerateFormat(Format));
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, &desc)))
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, D3DRTYPE_TEXTURE, &desc)))
return D3DERR_INVALIDCALL;
try {
@ -651,7 +665,7 @@ namespace dxvk {
|| (Usage & D3DUSAGE_DYNAMIC)
|| IsVendorFormat(EnumerateFormat(Format));
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, &desc)))
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, D3DRTYPE_VOLUMETEXTURE, &desc)))
return D3DERR_INVALIDCALL;
try {
@ -708,7 +722,7 @@ namespace dxvk {
|| (Usage & D3DUSAGE_DYNAMIC)
|| IsVendorFormat(EnumerateFormat(Format));
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, &desc)))
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, D3DRTYPE_CUBETEXTURE, &desc)))
return D3DERR_INVALIDCALL;
try {
@ -1034,6 +1048,9 @@ namespace dxvk {
if (srcTexInfo->Desc()->Format != dstTexInfo->Desc()->Format)
return D3DERR_INVALIDCALL;
if (src->GetSurfaceExtent() != dst->GetSurfaceExtent())
return D3DERR_INVALIDCALL;
if (dstTexInfo->Desc()->Pool == D3DPOOL_DEFAULT)
return this->StretchRect(pRenderTarget, nullptr, pDestSurface, nullptr, D3DTEXF_NONE);
@ -1077,7 +1094,12 @@ namespace dxvk {
if (unlikely(iSwapChain != 0))
return D3DERR_INVALIDCALL;
return m_implicitSwapchain->GetFrontBufferData(pDestSurface);
D3D9DeviceLock lock = LockDevice();
// In windowed mode, GetFrontBufferData takes a screenshot of the entire screen.
// We use the last used swapchain as a workaround.
// Total War: Medieval 2 relies on this.
return m_mostRecentlyUsedSwapchain->GetFrontBufferData(pDestSurface);
}
@ -1122,6 +1144,9 @@ namespace dxvk {
if (unlikely((srcSubresource.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) && m_flags.test(D3D9DeviceFlag::InScene)))
return D3DERR_INVALIDCALL;
if (unlikely(Filter != D3DTEXF_NONE && Filter != D3DTEXF_LINEAR && Filter != D3DTEXF_POINT))
return D3DERR_INVALIDCALL;
VkExtent3D srcExtent = srcImage->mipLevelExtent(srcSubresource.mipLevel);
VkExtent3D dstExtent = dstImage->mipLevelExtent(dstSubresource.mipLevel);
@ -1200,8 +1225,51 @@ namespace dxvk {
uint32_t(blitInfo.dstOffsets[1].y - blitInfo.dstOffsets[0].y),
uint32_t(blitInfo.dstOffsets[1].z - blitInfo.dstOffsets[0].z) };
bool srcIsDepth = IsDepthFormat(srcFormat);
bool dstIsDepth = IsDepthFormat(dstFormat);
if (unlikely(srcIsDepth || dstIsDepth)) {
if (unlikely(!srcIsDepth || !dstIsDepth))
return D3DERR_INVALIDCALL;
if (unlikely(srcTextureInfo->Desc()->Discard || dstTextureInfo->Desc()->Discard))
return D3DERR_INVALIDCALL;
if (unlikely(srcCopyExtent.width != srcExtent.width || srcCopyExtent.height != srcExtent.height))
return D3DERR_INVALIDCALL;
if (unlikely(m_flags.test(D3D9DeviceFlag::InScene)))
return D3DERR_INVALIDCALL;
}
// Copies would only work if the extents match. (ie. no stretching)
bool stretch = srcCopyExtent != dstCopyExtent;
bool dstHasRTUsage = (dstTextureInfo->Desc()->Usage & (D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL)) != 0;
bool dstIsSurface = dstTextureInfo->GetType() == D3DRTYPE_SURFACE;
if (stretch) {
if (unlikely(pSourceSurface == pDestSurface))
return D3DERR_INVALIDCALL;
if (unlikely(dstIsDepth))
return D3DERR_INVALIDCALL;
// The docs say that stretching is only allowed if the destination is either a render target surface or a render target texture.
// However in practice, using an offscreen plain surface in D3DPOOL_DEFAULT as the destination works fine.
// Using a texture without USAGE_RENDERTARGET as destination however does not.
if (unlikely(!dstIsSurface && !dstHasRTUsage))
return D3DERR_INVALIDCALL;
} else {
bool srcIsSurface = srcTextureInfo->GetType() == D3DRTYPE_SURFACE;
bool srcHasRTUsage = (srcTextureInfo->Desc()->Usage & (D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL)) != 0;
// Non-stretching copies are only allowed if:
// - the destination is either a render target surface or a render target texture
// - both destination and source are depth stencil surfaces
// - both destination and source are offscreen plain surfaces.
// The only way to get a surface with resource type D3DRTYPE_SURFACE without USAGE_RT or USAGE_DS is CreateOffscreenPlainSurface.
if (unlikely((!dstHasRTUsage && (!dstIsSurface || !srcIsSurface || srcHasRTUsage)) && !m_isD3D8Compatible))
return D3DERR_INVALIDCALL;
}
fastPath &= !stretch;
if (!fastPath || needsResolve) {
@ -1595,6 +1663,23 @@ namespace dxvk {
m_flags.clr(D3D9DeviceFlag::InScene);
// D3D9 resets the internally bound vertex buffers and index buffer in EndScene if they were unbound in the meantime.
// We have to ignore unbinding those buffers because of Operation Flashpoint Red River,
// so we should also clear the bindings here, to avoid leaks.
if (m_state.indices == nullptr) {
EmitCs([](DxvkContext* ctx) {
ctx->bindIndexBuffer(DxvkBufferSlice(), VK_INDEX_TYPE_UINT32);
});
}
for (uint32_t i = 0; i < caps::MaxStreams; i++) {
if (m_state.vertexBuffers[i].vertexBuffer == nullptr) {
EmitCs([cIndex = i](DxvkContext* ctx) {
ctx->bindVertexBuffer(cIndex, DxvkBufferSlice(), 0);
});
}
}
return D3D_OK;
}
@ -2360,7 +2445,8 @@ namespace dxvk {
try {
const Com<D3D9StateBlock> sb = new D3D9StateBlock(this, ConvertStateBlockType(Type));
*ppSB = sb.ref();
m_losableResourceCounter++;
if (!m_isD3D8Compatible)
m_losableResourceCounter++;
return D3D_OK;
}
@ -2392,7 +2478,8 @@ namespace dxvk {
return D3DERR_INVALIDCALL;
*ppSB = m_recorder.ref();
m_losableResourceCounter++;
if (!m_isD3D8Compatible)
m_losableResourceCounter++;
m_recorder = nullptr;
return D3D_OK;
@ -2610,7 +2697,21 @@ namespace dxvk {
if (unlikely(!PrimitiveCount))
return S_OK;
PrepareDraw(PrimitiveType);
bool dynamicSysmemVBOs;
uint32_t firstIndex = 0;
int32_t baseVertexIndex = 0;
uint32_t vertexCount = GetVertexCount(PrimitiveType, PrimitiveCount);
UploadDynamicSysmemBuffers(
StartVertex,
vertexCount,
firstIndex,
0,
baseVertexIndex,
&dynamicSysmemVBOs,
nullptr
);
PrepareDraw(PrimitiveType, !dynamicSysmemVBOs, false);
EmitCs([this,
cPrimType = PrimitiveType,
@ -2631,7 +2732,6 @@ namespace dxvk {
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9DeviceEx::DrawIndexedPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
INT BaseVertexIndex,
@ -2647,7 +2747,20 @@ namespace dxvk {
if (unlikely(!PrimitiveCount))
return S_OK;
PrepareDraw(PrimitiveType);
bool dynamicSysmemVBOs;
bool dynamicSysmemIBO;
uint32_t indexCount = GetVertexCount(PrimitiveType, PrimitiveCount);
UploadDynamicSysmemBuffers(
MinVertexIndex,
NumVertices,
StartIndex,
indexCount,
BaseVertexIndex,
&dynamicSysmemVBOs,
&dynamicSysmemIBO
);
PrepareDraw(PrimitiveType, !dynamicSysmemVBOs, !dynamicSysmemIBO);
EmitCs([this,
cPrimType = PrimitiveType,
@ -2683,7 +2796,7 @@ namespace dxvk {
if (unlikely(!PrimitiveCount))
return S_OK;
PrepareDraw(PrimitiveType);
PrepareDraw(PrimitiveType, false, false);
uint32_t vertexCount = GetVertexCount(PrimitiveType, PrimitiveCount);
@ -2735,7 +2848,7 @@ namespace dxvk {
if (unlikely(!PrimitiveCount))
return S_OK;
PrepareDraw(PrimitiveType);
PrepareDraw(PrimitiveType, false, false);
uint32_t vertexCount = GetVertexCount(PrimitiveType, PrimitiveCount);
@ -2822,7 +2935,7 @@ namespace dxvk {
D3D9CommonBuffer* dst = static_cast<D3D9VertexBuffer*>(pDestBuffer)->GetCommonBuffer();
D3D9VertexDecl* decl = static_cast<D3D9VertexDecl*> (pVertexDecl);
PrepareDraw(D3DPT_FORCE_DWORD);
PrepareDraw(D3DPT_FORCE_DWORD, true, true);
if (decl == nullptr) {
DWORD FVF = dst->Desc()->FVF;
@ -2837,19 +2950,24 @@ namespace dxvk {
decl = iter->second.ptr();
}
uint32_t offset = DestIndex * decl->GetSize();
uint32_t offset = DestIndex * decl->GetSize(0);
auto slice = dst->GetBufferSlice<D3D9_COMMON_BUFFER_TYPE_REAL>();
slice = slice.subSlice(offset, slice.length() - offset);
D3D9CompactVertexElements elements;
for (const D3DVERTEXELEMENT9& element : decl->GetElements()) {
elements.emplace_back(element);
}
EmitCs([this,
cDecl = ref(decl),
cVertexCount = VertexCount,
cStartIndex = SrcStartIndex,
cInstanceCount = GetInstanceCount(),
cBufferSlice = slice
cVertexElements = std::move(elements),
cVertexCount = VertexCount,
cStartIndex = SrcStartIndex,
cInstanceCount = GetInstanceCount(),
cBufferSlice = slice
](DxvkContext* ctx) mutable {
Rc<DxvkShader> shader = m_swvpEmulator.GetShaderModule(this, cDecl);
Rc<DxvkShader> shader = m_swvpEmulator.GetShaderModule(this, std::move(cVertexElements));
auto drawInfo = GenerateDrawInfo(D3DPT_POINTLIST, cVertexCount, cInstanceCount);
@ -2884,7 +3002,7 @@ namespace dxvk {
}
if (dst->GetMapMode() == D3D9_COMMON_BUFFER_MAP_MODE_BUFFER) {
uint32_t copySize = VertexCount * decl->GetSize();
uint32_t copySize = VertexCount * decl->GetSize(0);
EmitCs([
cSrcBuffer = dst->GetBuffer<D3D9_COMMON_BUFFER_TYPE_REAL>(),
@ -3220,6 +3338,11 @@ namespace dxvk {
vbo.offset = OffsetInBytes;
vbo.stride = Stride;
} else {
// D3D9 doesn't actually unbind any vertex buffer when passing null.
// Operation Flashpoint: Red River relies on this behavior.
needsUpdate = false;
vbo.offset = 0;
}
if (needsUpdate)
@ -3325,7 +3448,8 @@ namespace dxvk {
m_state.indices = buffer;
BindIndices();
if (buffer != nullptr)
BindIndices();
return D3D_OK;
}
@ -3726,7 +3850,7 @@ namespace dxvk {
desc.IsAttachmentOnly = TRUE;
desc.IsLockable = Lockable;
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, &desc)))
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, D3DRTYPE_TEXTURE, &desc)))
return D3DERR_INVALIDCALL;
try {
@ -3774,7 +3898,7 @@ namespace dxvk {
// Docs: Off-screen plain surfaces are always lockable, regardless of their pool types.
desc.IsLockable = TRUE;
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, &desc)))
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, D3DRTYPE_TEXTURE, &desc)))
return D3DERR_INVALIDCALL;
if (pSharedHandle != nullptr && Pool != D3DPOOL_DEFAULT)
@ -3829,7 +3953,7 @@ namespace dxvk {
// Docs don't say anything, so just assume it's lockable.
desc.IsLockable = TRUE;
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, &desc)))
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, D3DRTYPE_TEXTURE, &desc)))
return D3DERR_INVALIDCALL;
try {
@ -5087,6 +5211,193 @@ namespace dxvk {
}
void D3D9DeviceEx::UploadDynamicSysmemBuffers(
UINT& FirstVertexIndex,
UINT NumVertices,
UINT& FirstIndex,
UINT NumIndices,
INT& BaseVertexIndex,
bool* pDynamicVBOs,
bool* pDynamicIBO
) {
bool dynamicSysmemVBOs = true;
for (uint32_t i = 0; i < caps::MaxStreams && dynamicSysmemVBOs; i++) {
auto* vbo = GetCommonBuffer(m_state.vertexBuffers[i].vertexBuffer);
dynamicSysmemVBOs &= vbo == nullptr || vbo->IsSysmemDynamic();
}
D3D9CommonBuffer* ibo = GetCommonBuffer(m_state.indices);
bool dynamicSysmemIBO = NumIndices != 0 && ibo != nullptr && ibo->IsSysmemDynamic();
*pDynamicVBOs = dynamicSysmemVBOs;
if (pDynamicIBO)
*pDynamicIBO = dynamicSysmemIBO;
if (likely(!dynamicSysmemVBOs && !dynamicSysmemIBO))
return;
// The UP buffer allocator will invalidate,
// so we can only use 1 UP buffer slice per draw.
// First we calculate the size of that UP buffer slice
// and store all sizes and offsets into it.
struct VBOCopy {
uint32_t srcOffset;
uint32_t dstOffset;
uint32_t copyBufferLength;
uint32_t copyElementCount;
uint32_t copyElementSize;
uint32_t copyElementStride;
};
uint32_t totalUpBufferSize = 0;
std::array<VBOCopy, caps::MaxStreams> vboCopies = {};
for (uint32_t i = 0; i < caps::MaxStreams && dynamicSysmemVBOs; i++) {
auto* vbo = GetCommonBuffer(m_state.vertexBuffers[i].vertexBuffer);
if (likely(vbo == nullptr)) {
continue;
}
const uint32_t vertexSize = m_state.vertexDecl->GetSize(i);
const uint32_t vertexStride = m_state.vertexBuffers[i].stride;
const uint32_t srcStride = vertexStride;
const uint32_t dstStride = std::min(vertexStride, vertexSize);
uint32_t elementCount = NumVertices;
if (m_state.streamFreq[i] & D3DSTREAMSOURCE_INSTANCEDATA) {
elementCount = GetInstanceCount();
}
const uint32_t vboOffset = m_state.vertexBuffers[i].offset;
const uint32_t vertexOffset = (FirstVertexIndex + BaseVertexIndex) * srcStride;
const uint32_t vertexBufferSize = vbo->Desc()->Size;
const uint32_t srcOffset = vboOffset + vertexOffset;
if (unlikely(srcOffset > vertexBufferSize)) {
// All vertices are out of bounds
vboCopies[i].copyBufferLength = 0;
} else if (unlikely(srcOffset + elementCount * srcStride > vertexBufferSize)) {
// Some vertices are (partially) out of bounds
uint32_t boundVertexBufferRange = vertexBufferSize - vboOffset;
elementCount = boundVertexBufferRange / srcStride;
// Copy all complete vertices
vboCopies[i].copyBufferLength = elementCount * dstStride;
// Copy the remaining partial vertex
vboCopies[i].copyBufferLength += std::min(dstStride, boundVertexBufferRange % srcStride);
} else {
// No vertices are out of bounds
vboCopies[i].copyBufferLength = elementCount * dstStride;
}
vboCopies[i].copyElementCount = elementCount;
vboCopies[i].copyElementStride = srcStride;
vboCopies[i].copyElementSize = dstStride;
vboCopies[i].srcOffset = srcOffset;
vboCopies[i].dstOffset = totalUpBufferSize;
totalUpBufferSize += vboCopies[i].copyBufferLength;
}
uint32_t iboUPBufferSize = 0;
uint32_t iboUPBufferOffset = 0;
if (dynamicSysmemIBO) {
auto* ibo = GetCommonBuffer(m_state.indices);
if (likely(ibo != nullptr)) {
uint32_t indexStride = ibo->Desc()->Format == D3D9Format::INDEX16 ? 2 : 4;
uint32_t offset = indexStride * FirstIndex;
uint32_t indexBufferSize = ibo->Desc()->Size;
if (offset < indexBufferSize) {
iboUPBufferSize = std::min(NumIndices * indexStride, indexBufferSize - offset);
iboUPBufferOffset = totalUpBufferSize;
totalUpBufferSize += iboUPBufferSize;
}
}
}
if (unlikely(totalUpBufferSize == 0)) {
*pDynamicVBOs = false;
if (pDynamicIBO)
*pDynamicIBO = false;
return;
}
auto upSlice = AllocUPBuffer(totalUpBufferSize);
// Now copy the actual data and bind it.
for (uint32_t i = 0; i < caps::MaxStreams && dynamicSysmemVBOs; i++) {
const VBOCopy& copy = vboCopies[i];
if (likely(copy.copyBufferLength != 0)) {
const auto* vbo = GetCommonBuffer(m_state.vertexBuffers[i].vertexBuffer);
uint8_t* data = reinterpret_cast<uint8_t*>(upSlice.mapPtr) + copy.dstOffset;
const uint8_t* src = reinterpret_cast<uint8_t*>(vbo->GetMappedSlice().mapPtr) + copy.srcOffset;
if (likely(copy.copyElementStride == copy.copyElementSize)) {
std::memcpy(data, src, copy.copyBufferLength);
} else {
for (uint32_t j = 0; j < copy.copyElementCount; j++) {
std::memcpy(data + j * copy.copyElementSize, src + j * copy.copyElementStride, copy.copyElementSize);
}
if (unlikely(copy.copyBufferLength > copy.copyElementCount * copy.copyElementSize)) {
// Partial vertex at the end
std::memcpy(
data + copy.copyElementCount * copy.copyElementSize,
src + copy.copyElementCount * copy.copyElementStride,
copy.copyBufferLength - copy.copyElementCount * copy.copyElementSize);
}
}
}
auto vboSlice = upSlice.slice.subSlice(copy.dstOffset, copy.copyBufferLength);
EmitCs([
cStream = i,
cBufferSlice = std::move(vboSlice),
cStride = copy.copyElementSize
](DxvkContext* ctx) mutable {
ctx->bindVertexBuffer(cStream, std::move(cBufferSlice), cStride);
});
m_flags.set(D3D9DeviceFlag::DirtyVertexBuffers);
}
if (dynamicSysmemVBOs) {
// Change the draw call parameters to reflect the changed vertex buffers
if (NumIndices != 0) {
BaseVertexIndex = -FirstVertexIndex;
} else {
FirstVertexIndex = 0;
}
}
if (dynamicSysmemIBO) {
if (unlikely(iboUPBufferSize == 0)) {
EmitCs([](DxvkContext* ctx) {
ctx->bindIndexBuffer(DxvkBufferSlice(), VK_INDEX_TYPE_UINT32);
});
m_flags.set(D3D9DeviceFlag::DirtyIndexBuffer);
} else {
auto* ibo = GetCommonBuffer(m_state.indices);
uint32_t indexStride = ibo->Desc()->Format == D3D9Format::INDEX16 ? 2 : 4;
VkIndexType indexType = DecodeIndexType(ibo->Desc()->Format);
uint32_t offset = indexStride * FirstIndex;
uint8_t* data = reinterpret_cast<uint8_t*>(upSlice.mapPtr) + iboUPBufferOffset;
uint8_t* src = reinterpret_cast<uint8_t*>(ibo->GetMappedSlice().mapPtr) + offset;
std::memcpy(data, src, iboUPBufferSize);
auto iboSlice = upSlice.slice.subSlice(iboUPBufferOffset, iboUPBufferSize);
EmitCs([
cBufferSlice = std::move(iboSlice),
cIndexType = indexType
](DxvkContext* ctx) mutable {
ctx->bindIndexBuffer(std::move(cBufferSlice), cIndexType);
});
m_flags.set(D3D9DeviceFlag::DirtyIndexBuffer);
}
// Change the draw call parameters to reflect the changed index buffer
FirstIndex = 0;
}
}
void D3D9DeviceEx::EmitCsChunk(DxvkCsChunkRef&& chunk) {
m_csSeqNum = m_csThread.dispatchChunk(std::move(chunk));
}
@ -5133,7 +5444,7 @@ namespace dxvk {
// Round to nearest
_controlfp(_RC_NEAR, _MCW_RC);
#elif (defined(__GNUC__) || defined(__MINGW32__)) && (defined(__i386__) || defined(__x86_64__) || defined(__ia64))
#elif (defined(__GNUC__) || defined(__MINGW32__)) && (defined(__i386__) || (defined(__x86_64__) && !defined(__arm64ec__)) || defined(__ia64))
// For GCC/MinGW we can use inline asm to set it.
// This only works for x86 and x64 processors however.
@ -5255,7 +5566,7 @@ namespace dxvk {
uint32_t floatCount = m_vsFloatConstsCount;
if (constSet.meta.needsConstantCopies) {
auto shader = GetCommonShader(m_state.vertexShader);
floatCount = std::max(floatCount, shader->GetMaxDefinedConstant());
floatCount = std::max(floatCount, shader->GetMaxDefinedConstant() + 1);
}
floatCount = std::min(floatCount, constSet.meta.maxConstIndexF);
@ -5317,7 +5628,7 @@ namespace dxvk {
uint32_t floatCount = ShaderStage == DxsoProgramType::VertexShader ? m_vsFloatConstsCount : m_psFloatConstsCount;
if (constSet.meta.needsConstantCopies) {
auto shader = GetCommonShader(Shader);
floatCount = std::max(floatCount, shader->GetMaxDefinedConstant());
floatCount = std::max(floatCount, shader->GetMaxDefinedConstant() + 1);
}
floatCount = std::min(constSet.meta.maxConstIndexF, floatCount);
@ -5450,10 +5761,13 @@ namespace dxvk {
}
void D3D9DeviceEx::Flush() {
template <bool Synchronize9On12>
void D3D9DeviceEx::ExecuteFlush() {
D3D9DeviceLock lock = LockDevice();
if constexpr (Synchronize9On12)
m_submitStatus.result = VK_NOT_READY;
m_initializer->Flush();
m_converter->Flush();
@ -5465,16 +5779,32 @@ namespace dxvk {
EmitCs<false>([
cSubmissionFence = m_submissionFence,
cSubmissionId = submissionId
cSubmissionId = submissionId,
cSubmissionStatus = Synchronize9On12 ? &m_submitStatus : nullptr
] (DxvkContext* ctx) {
ctx->signal(cSubmissionFence, cSubmissionId);
ctx->flushCommandList(nullptr);
ctx->flushCommandList(cSubmissionStatus);
});
FlushCsChunk();
m_flushSeqNum = m_csSeqNum;
m_flushTracker.notifyFlush(m_flushSeqNum, submissionId);
// If necessary, block calling thread until the
// Vulkan queue submission is performed.
if constexpr (Synchronize9On12)
m_dxvkDevice->waitForSubmission(&m_submitStatus);
}
void D3D9DeviceEx::Flush() {
ExecuteFlush<false>();
}
void D3D9DeviceEx::FlushAndSync9On12() {
ExecuteFlush<true>();
}
@ -6449,7 +6779,7 @@ namespace dxvk {
}
void D3D9DeviceEx::PrepareDraw(D3DPRIMITIVETYPE PrimitiveType) {
void D3D9DeviceEx::PrepareDraw(D3DPRIMITIVETYPE PrimitiveType, bool UploadVBOs, bool UploadIBO) {
if (unlikely(m_activeHazardsRT != 0 || m_activeHazardsDS != 0))
MarkRenderHazards();
@ -6462,7 +6792,7 @@ namespace dxvk {
for (uint32_t i = 0; i < caps::MaxStreams; i++) {
auto* vbo = GetCommonBuffer(m_state.vertexBuffers[i].vertexBuffer);
if (vbo != nullptr && vbo->NeedsUpload())
if (vbo != nullptr && vbo->NeedsUpload() && UploadVBOs)
FlushBuffer(vbo);
}
@ -6478,7 +6808,7 @@ namespace dxvk {
GenerateTextureMips(texturesToGen);
auto* ibo = GetCommonBuffer(m_state.indices);
if (ibo != nullptr && ibo->NeedsUpload())
if (ibo != nullptr && ibo->NeedsUpload() && UploadIBO)
FlushBuffer(ibo);
UpdateFog();
@ -6611,6 +6941,19 @@ namespace dxvk {
}
BindSpecConstants();
if (unlikely(m_flags.test(D3D9DeviceFlag::DirtyVertexBuffers) && UploadVBOs)) {
for (uint32_t i = 0; i < caps::MaxStreams; i++) {
const D3D9VBO& vbo = m_state.vertexBuffers[i];
BindVertexBuffer(i, vbo.vertexBuffer.ptr(), vbo.offset, vbo.stride);
}
m_flags.clr(D3D9DeviceFlag::DirtyVertexBuffers);
}
if (unlikely(m_flags.test(D3D9DeviceFlag::DirtyIndexBuffer) && UploadIBO)) {
BindIndices();
m_flags.clr(D3D9DeviceFlag::DirtyIndexBuffer);
}
}
@ -6981,6 +7324,7 @@ namespace dxvk {
key.Data.Contents.TransformFlags |= transformFlags << (i * 3);
key.Data.Contents.TexcoordFlags |= indexFlags << (i * 3);
key.Data.Contents.TexcoordIndices |= index << (i * 3);
key.Data.Contents.Projected |= ((m_state.textureStages[i][DXVK_TSS_TEXTURETRANSFORMFLAGS] & D3DTTFF_PROJECTED) == D3DTTFF_PROJECTED) << i;
}
key.Data.Contents.TexcoordDeclMask = m_state.vertexDecl != nullptr ? m_state.vertexDecl->GetTexcoordMask() : 0;
@ -7151,6 +7495,8 @@ namespace dxvk {
stage.Projected = (ttff & D3DTTFF_PROJECTED) ? 1 : 0;
stage.ProjectedCount = (ttff & D3DTTFF_PROJECTED) ? count : 0;
stage.SampleDref = (m_depthTextures & (1 << idx)) != 0;
}
auto& stage0 = key.Stages[0].Contents;
@ -7433,6 +7779,8 @@ namespace dxvk {
rs[D3DRS_CLIPPLANEENABLE] = 0;
m_flags.set(D3D9DeviceFlag::DirtyClipPlanes);
const VkPhysicalDeviceLimits& limits = m_dxvkDevice->adapter()->deviceProperties().limits;
rs[D3DRS_POINTSPRITEENABLE] = FALSE;
rs[D3DRS_POINTSCALEENABLE] = FALSE;
rs[D3DRS_POINTSCALE_A] = bit::cast<DWORD>(1.0f);
@ -7440,7 +7788,7 @@ namespace dxvk {
rs[D3DRS_POINTSCALE_C] = bit::cast<DWORD>(0.0f);
rs[D3DRS_POINTSIZE] = bit::cast<DWORD>(1.0f);
rs[D3DRS_POINTSIZE_MIN] = bit::cast<DWORD>(1.0f);
rs[D3DRS_POINTSIZE_MAX] = bit::cast<DWORD>(64.0f);
rs[D3DRS_POINTSIZE_MAX] = bit::cast<DWORD>(limits.pointSizeRange[1]);
UpdatePushConstant<D3D9RenderStateItem::PointSize>();
UpdatePushConstant<D3D9RenderStateItem::PointSizeMin>();
UpdatePushConstant<D3D9RenderStateItem::PointSizeMax>();
@ -7614,8 +7962,10 @@ namespace dxvk {
if (FAILED(hr))
return hr;
}
else
else {
m_implicitSwapchain = new D3D9SwapChainEx(this, pPresentationParameters, pFullscreenDisplayMode);
m_mostRecentlyUsedSwapchain = m_implicitSwapchain.ptr();
}
if (pPresentationParameters->EnableAutoDepthStencil) {
D3D9_COMMON_TEXTURE_DESC desc;
@ -7635,7 +7985,7 @@ namespace dxvk {
// Docs: Also note that - unlike textures - swap chain back buffers, render targets [..] can be locked
desc.IsLockable = TRUE;
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, &desc)))
if (FAILED(D3D9CommonTexture::NormalizeTextureProperties(this, D3DRTYPE_TEXTURE, &desc)))
return D3DERR_NOTAVAILABLE;
m_autoDepthStencil = new D3D9Surface(this, &desc, nullptr, nullptr);

View File

@ -66,6 +66,8 @@ namespace dxvk {
DirtyInputLayout,
DirtyViewportScissor,
DirtyMultiSampleState,
DirtyVertexBuffers,
DirtyIndexBuffer,
DirtyFogState,
DirtyFogColor,
@ -764,6 +766,24 @@ namespace dxvk {
HRESULT UnlockBuffer(
D3D9CommonBuffer* pResource);
/**
* @brief Uploads data from D3DPOOL_SYSMEM + D3DUSAGE_DYNAMIC buffers and binds the temporary buffers.
*
* @param FirstVertexIndex The first vertex
* @param NumVertices The number of vertices that are accessed. If this is 0, the vertex buffer binding will not be modified.
* @param FirstIndex The first index
* @param NumIndices The number of indices that will be drawn. If this is 0, the index buffer binding will not be modified.
*/
void UploadDynamicSysmemBuffers(
UINT& FirstVertexIndex,
UINT NumVertices,
UINT& FirstIndex,
UINT NumIndices,
INT& BaseVertexIndex,
bool* pDynamicVBOs,
bool* pDynamicIBO);
void SetupFPU();
int64_t DetermineInitialTextureMemory();
@ -773,6 +793,7 @@ namespace dxvk {
void SynchronizeCsThread(uint64_t SequenceNumber);
void Flush();
void FlushAndSync9On12();
void EndFrame();
@ -816,7 +837,7 @@ namespace dxvk {
inline bool IsAlphaToCoverageEnabled() {
const bool alphaTest = m_state.renderStates[D3DRS_ALPHATESTENABLE] != 0;
return m_amdATOC || (m_nvATOC && alphaTest);
return (m_amdATOC || (m_nvATOC && alphaTest)) && m_flags.test(D3D9DeviceFlag::ValidSampleMask);
}
inline bool IsDepthBiasEnabled() {
@ -895,7 +916,7 @@ namespace dxvk {
uint32_t GetInstanceCount() const;
void PrepareDraw(D3DPRIMITIVETYPE PrimitiveType);
void PrepareDraw(D3DPRIMITIVETYPE PrimitiveType, bool UploadVBOs, bool UploadIBOs);
template <DxsoProgramType ShaderStage>
void BindShader(
@ -970,6 +991,17 @@ namespace dxvk {
void TouchMappedTexture(D3D9CommonTexture* pTexture);
void RemoveMappedTexture(D3D9CommonTexture* pTexture);
bool IsD3D8Compatible() const {
return m_isD3D8Compatible;
}
void SetD3D8CompatibilityMode(bool compatMode) {
if (compatMode)
Logger::info("The D3D9 device is now operating in D3D8 compatibility mode.");
m_isD3D8Compatible = compatMode;
}
// Device Lost
bool IsDeviceLost() const {
return m_deviceLostState != D3D9DeviceLostState::Ok;
@ -1022,6 +1054,15 @@ namespace dxvk {
bool CanSWVP() const {
return m_behaviorFlags & (D3DCREATE_MIXED_VERTEXPROCESSING | D3DCREATE_SOFTWARE_VERTEXPROCESSING);
}
// Device Reset detection for D3D9SwapChainEx::Present
bool IsDeviceReset() {
return std::exchange(m_deviceHasBeenReset, false);
}
template <bool Synchronize9On12>
void ExecuteFlush();
void DetermineConstantLayouts(bool canSWVP);
D3D9BufferSlice AllocUPBuffer(VkDeviceSize size);
@ -1046,7 +1087,7 @@ namespace dxvk {
}
inline uint32_t GetUPBufferSize(uint32_t vertexCount, uint32_t stride) {
return (vertexCount - 1) * stride + std::max(m_state.vertexDecl->GetSize(), stride);
return (vertexCount - 1) * stride + std::max(m_state.vertexDecl->GetSize(0), stride);
}
inline void FillUPVertexBuffer(void* buffer, const void* userData, uint32_t dataSize, uint32_t bufferSize) {
@ -1204,6 +1245,34 @@ namespace dxvk {
uint64_t GetCurrentSequenceNumber();
/**
* @brief Get the swapchain that was used the most recently for presenting
* Has to be externally synchronized.
*
* @return D3D9SwapChainEx* Swapchain
*/
D3D9SwapChainEx* GetMostRecentlyUsedSwapchain() {
return m_mostRecentlyUsedSwapchain;
}
/**
* @brief Set the swapchain that was used the most recently for presenting
* Has to be externally synchronized.
*
* @param swapchain Swapchain
*/
void SetMostRecentlyUsedSwapchain(D3D9SwapChainEx* swapchain) {
m_mostRecentlyUsedSwapchain = swapchain;
}
/**
* @brief Reset the most recently swapchain back to the implicit one
* Has to be externally synchronized.
*/
void ResetMostRecentlyUsedSwapchain() {
m_mostRecentlyUsedSwapchain = m_implicitSwapchain.ptr();
}
Com<D3D9InterfaceEx> m_parent;
D3DDEVTYPE m_deviceType;
HWND m_window;
@ -1318,13 +1387,15 @@ namespace dxvk {
D3D9ShaderMasks m_psShaderMasks = FixedFunctionMask;
bool m_isSWVP;
bool m_amdATOC = false;
bool m_nvATOC = false;
bool m_ffZTest = false;
bool m_isD3D8Compatible = false;
bool m_amdATOC = false;
bool m_nvATOC = false;
bool m_ffZTest = false;
VkImageLayout m_hazardLayout = VK_IMAGE_LAYOUT_GENERAL;
bool m_usingGraphicsPipelines = false;
bool m_deviceHasBeenReset = false;
DxvkDepthBiasRepresentation m_depthBiasRepresentation = { VK_DEPTH_BIAS_REPRESENTATION_LEAST_REPRESENTABLE_VALUE_FORMAT_EXT, false };
float m_depthBiasScale = 0.0f;
@ -1354,6 +1425,8 @@ namespace dxvk {
Rc<sync::Fence> m_submissionFence;
uint64_t m_submissionId = 0ull;
DxvkSubmitStatus m_submitStatus;
uint64_t m_flushSeqNum = 0ull;
GpuFlushTracker m_flushTracker;
@ -1364,6 +1437,8 @@ namespace dxvk {
HWND m_fullscreenWindow = NULL;
std::atomic<uint32_t> m_losableResourceCounter = { 0 };
D3D9SwapChainEx* m_mostRecentlyUsedSwapchain = nullptr;
#ifdef D3D9_ALLOW_UNMAPPING
lru_list<D3D9CommonTexture*> m_mappedTextures;
#endif

View File

@ -15,6 +15,7 @@ namespace dxvk {
D3D9FixedFunctionOptions::D3D9FixedFunctionOptions(const D3D9Options* options) {
invariantPosition = options->invariantPosition;
forceSampleRateShading = options->forceSampleRateShading;
drefScaling = options->drefScaling;
}
uint32_t DoFixedFunctionFog(D3D9ShaderSpecConstantManager& spec, SpirvModule& spvModule, const D3D9FogContext& fogCtx) {
@ -336,7 +337,7 @@ namespace dxvk {
}
uint32_t SetupRenderStateBlock(SpirvModule& spvModule, uint32_t count) {
uint32_t SetupRenderStateBlock(SpirvModule& spvModule) {
uint32_t floatType = spvModule.defFloatType(32);
uint32_t uintType = spvModule.defIntType(32, 0);
uint32_t vec3Type = spvModule.defVectorType(floatType, 3);
@ -357,7 +358,7 @@ namespace dxvk {
floatType,
}};
uint32_t rsStruct = spvModule.defStructTypeUnique(count, rsMembers.data());
uint32_t rsStruct = spvModule.defStructTypeUnique(rsMembers.size(), rsMembers.data());
uint32_t rsBlock = spvModule.newVar(
spvModule.defPointerType(rsStruct, spv::StorageClassPushConstant),
spv::StorageClassPushConstant);
@ -369,9 +370,6 @@ namespace dxvk {
uint32_t memberIdx = 0;
auto SetMemberName = [&](const char* name, uint32_t offset) {
if (memberIdx >= count)
return;
spvModule.setDebugMemberName (rsStruct, memberIdx, name);
spvModule.memberDecorateOffset (rsStruct, memberIdx, offset);
memberIdx++;
@ -781,8 +779,6 @@ namespace dxvk {
uint32_t m_inputMask = 0u;
uint32_t m_outputMask = 0u;
uint32_t m_flatShadingMask = 0u;
uint32_t m_pushConstOffset = 0u;
uint32_t m_pushConstSize = 0u;
DxsoProgramType m_programType;
D3D9FFShaderKeyVS m_vsKey;
@ -892,8 +888,8 @@ namespace dxvk {
info.inputMask = m_inputMask;
info.outputMask = m_outputMask;
info.flatShadingInputs = m_flatShadingMask;
info.pushConstOffset = m_pushConstOffset;
info.pushConstSize = m_pushConstSize;
info.pushConstStages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
info.pushConstSize = sizeof(D3D9RenderStateInfo);
return new DxvkShader(info, m_module.compile());
}
@ -1111,31 +1107,63 @@ namespace dxvk {
const uint32_t wIndex = 3;
uint32_t flags = (m_vsKey.Data.Contents.TransformFlags >> (i * 3)) & 0b111;
uint32_t count;
// 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;
uint32_t count = std::min(flags, 4u);
// A projection component index of 4 means we won't do projection
uint32_t projIndex = count != 0 ? count - 1 : 4;
switch (inputFlags) {
default:
case (DXVK_TSS_TCI_PASSTHRU >> TCIOffset):
transformed = m_vs.in.TEXCOORD[inputIndex & 0xFF];
// flags is actually the number of elements that get passed
// to the rasterizer.
count = flags;
if (texcoordCount) {
// Clamp by the number of elements in the texcoord input.
if (!count || count > texcoordCount)
count = texcoordCount;
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 = m_module.opCompositeInsert(m_vec4Type, m_module.constf32(0), transformed, 1, &wIndex);
}
else
flags = D3DTTFF_DISABLE;
if (applyTransform && !m_vsKey.Data.Contents.HasPositionT) {
/*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
uint32_t idx = 1;
transformed = m_module.opCompositeInsert(m_vec4Type, m_module.constf32(1), transformed, 1, &idx);
}*/
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.
uint32_t idx = texcoordCount;
transformed = m_module.opCompositeInsert(m_vec4Type, m_module.constf32(1), transformed, 1, &idx);
}
} 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;
count = 4;
if (!applyTransform) {
count = 3;
projIndex = 4;
}
break;
case (DXVK_TSS_TCI_CAMERASPACEPOSITION >> TCIOffset):
transformed = m_module.opCompositeInsert(m_vec4Type, m_module.constf32(1.0f), vtx, 1, &wIndex);
count = 4;
transformed = vtx;
if (!applyTransform) {
Logger::warn(str::format("!applyTransform flags: ", flags, " projidx: ", projIndex));
count = 3;
projIndex = 4;
}
break;
case (DXVK_TSS_TCI_CAMERASPACEREFLECTIONVECTOR >> TCIOffset): {
@ -1150,7 +1178,10 @@ namespace dxvk {
transformIndices[3] = m_module.constf32(1.0f);
transformed = m_module.opCompositeConstruct(m_vec4Type, transformIndices.size(), transformIndices.data());
count = 4;
if (!applyTransform) {
count = 3;
projIndex = 4;
}
break;
}
@ -1174,35 +1205,27 @@ namespace dxvk {
transformIndices[3] = m_module.constf32(1.0f);
transformed = m_module.opCompositeConstruct(m_vec4Type, transformIndices.size(), transformIndices.data());
count = 4;
break;
}
}
uint32_t type = flags;
if (type != D3DTTFF_DISABLE) {
if (!m_vsKey.Data.Contents.HasPositionT) {
for (uint32_t j = count; j < 4; j++) {
// If we're outside the component count of the vertex decl for this texcoord then we pad with zeroes.
// Otherwise, pad with ones.
if (applyTransform && !m_vsKey.Data.Contents.HasPositionT) {
transformed = m_module.opVectorTimesMatrix(m_vec4Type, transformed, m_vs.constants.texcoord[i]);
}
// Very weird quirk in order to get texcoord transforms to work like they do in native.
// In future, maybe we could sort this out properly by chopping matrices of different sizes, but thats
// a project for another day.
uint32_t texcoordCount = (m_vsKey.Data.Contents.TexcoordDeclMask >> (3 * inputIndex)) & 0x7;
uint32_t value = j > texcoordCount ? m_module.constf32(0) : m_module.constf32(1);
transformed = m_module.opCompositeInsert(m_vec4Type, value, transformed, 1, &j);
}
if (m_vsKey.Data.Contents.Projected && projIndex < 4) {
// The projection idx is always based on the flags, even when the input mode is not DXVK_TSS_TCI_PASSTHRU.
uint32_t projValue = m_module.opCompositeExtract(m_floatType, transformed, 1, &projIndex);
transformed = m_module.opVectorTimesMatrix(m_vec4Type, transformed, m_vs.constants.texcoord[i]);
}
// 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 = m_module.opCompositeInsert(m_vec4Type, projValue, transformed, 1, &wIndex);
}
// Pad the unused section of it with the value for projection.
uint32_t lastIdx = count - 1;
uint32_t projValue = m_module.opCompositeExtract(m_floatType, transformed, 1, &lastIdx);
for (uint32_t j = count; j < 4; j++)
transformed = m_module.opCompositeInsert(m_vec4Type, projValue, transformed, 1, &j);
uint32_t totalComponents = (m_vsKey.Data.Contents.Projected && projIndex < 4) ? 3 : 4;
for (uint32_t i = count; i < totalComponents; i++) {
// Discard the components that exceed the specified D3DTTFF_COUNT
transformed = m_module.opCompositeInsert(m_vec4Type, m_module.constf32(0), transformed, 1, &i);
}
m_module.opStore(m_vs.out.TEXCOORD[i], transformed);
@ -1315,6 +1338,8 @@ namespace dxvk {
uint32_t midDot = m_module.opDot(m_floatType, normal, mid);
midDot = m_module.opFClamp(m_floatType, midDot, m_module.constf32(0.0f), m_module.constf32(1.0f));
uint32_t doSpec = m_module.opFOrdGreaterThan(bool_t, midDot, m_module.constf32(0.0f));
doSpec = m_module.opLogicalAnd(bool_t, doSpec, m_module.opFOrdGreaterThan(bool_t, hitDot, m_module.constf32(0.0f)));
uint32_t specularness = m_module.opPow(m_floatType, midDot, m_vs.constants.materialPower);
specularness = m_module.opFMul(m_floatType, specularness, atten);
specularness = m_module.opSelect(m_floatType, doSpec, specularness, m_module.constf32(0.0f));
@ -1384,20 +1409,7 @@ namespace dxvk {
void D3D9FFShaderCompiler::setupRenderStateInfo() {
uint32_t count;
if (m_programType == DxsoProgramType::PixelShader) {
m_pushConstOffset = 0;
m_pushConstSize = offsetof(D3D9RenderStateInfo, pointSize);
count = 5;
}
else {
m_pushConstOffset = offsetof(D3D9RenderStateInfo, pointSize);
m_pushConstSize = sizeof(float) * 6;
count = 11;
}
m_rsBlock = SetupRenderStateBlock(m_module, count);
m_rsBlock = SetupRenderStateBlock(m_module);
}
@ -1789,6 +1801,11 @@ namespace dxvk {
return coords;
};
auto ScalarReplicate = [&](uint32_t reg) {
std::array<uint32_t, 4> replicant = { reg, reg, reg, reg };
return m_module.opCompositeConstruct(m_vec4Type, replicant.size(), replicant.data());
};
auto GetTexture = [&]() {
if (!processedTexture) {
SpirvImageOperands imageOperands;
@ -1808,21 +1825,17 @@ namespace dxvk {
texcoord = m_module.opVectorShuffle(texcoord_t,
texcoord, texcoord, texcoordCnt, indices.data());
uint32_t projIdx = m_fsKey.Stages[i].Contents.ProjectedCount;
if (projIdx == 0 || projIdx > texcoordCnt)
projIdx = texcoordCnt;
--projIdx;
bool shouldProject = m_fsKey.Stages[i].Contents.Projected;
uint32_t projValue = 0;
if (m_fsKey.Stages[i].Contents.Projected) {
if (shouldProject) {
// Always use w, the vertex shader puts the correct value there.
const uint32_t projIdx = 3;
projValue = m_module.opCompositeExtract(m_floatType, m_ps.in.TEXCOORD[i], 1, &projIdx);
uint32_t insertIdx = texcoordCnt - 1;
texcoord = m_module.opCompositeInsert(texcoord_t, projValue, texcoord, 1, &insertIdx);
}
bool shouldProject = m_fsKey.Stages[i].Contents.Projected;
if (i != 0 && (
m_fsKey.Stages[i - 1].Contents.ColorOp == D3DTOP_BUMPENVMAP ||
m_fsKey.Stages[i - 1].Contents.ColorOp == D3DTOP_BUMPENVMAPLUMINANCE)) {
@ -1836,10 +1849,23 @@ namespace dxvk {
shouldProject = false;
}
if (shouldProject)
if (unlikely(stage.SampleDref)) {
uint32_t component = 2;
uint32_t reference = m_module.opCompositeExtract(m_floatType, texcoord, 1, &component);
// [D3D8] Scale Dref to [0..(2^N - 1)] for D24S8 and D16 if Dref scaling is enabled
if (m_options.drefScaling) {
uint32_t maxDref = m_module.constf32(1.0f / (float(1 << m_options.drefScaling) - 1.0f));
reference = m_module.opFMul(m_floatType, reference, maxDref);
}
texture = m_module.opImageSampleDrefImplicitLod(m_floatType, imageVarId, texcoord, reference, imageOperands);
texture = ScalarReplicate(texture);
} else if (shouldProject) {
texture = m_module.opImageSampleProjImplicitLod(m_vec4Type, imageVarId, texcoord, imageOperands);
else
} else {
texture = m_module.opImageSampleImplicitLod(m_vec4Type, imageVarId, texcoord, imageOperands);
}
if (i != 0 && m_fsKey.Stages[i - 1].Contents.ColorOp == D3DTOP_BUMPENVMAPLUMINANCE) {
uint32_t index = m_module.constu32(D3D9SharedPSStages_Count * (i - 1) + D3D9SharedPSStages_BumpEnvLScale);
@ -1867,11 +1893,6 @@ namespace dxvk {
return texture;
};
auto ScalarReplicate = [&](uint32_t reg) {
std::array<uint32_t, 4> replicant = { reg, reg, reg, reg };
return m_module.opCompositeConstruct(m_vec4Type, replicant.size(), replicant.data());
};
auto AlphaReplicate = [&](uint32_t reg) {
uint32_t alphaComponentId = 3;
uint32_t alpha = m_module.opCompositeExtract(m_floatType, reg, 1, &alphaComponentId);
@ -2268,6 +2289,11 @@ namespace dxvk {
dimensionality = spv::Dim2D;
sampler.texcoordCnt = 2;
viewType = VK_IMAGE_VIEW_TYPE_2D;
// Z coordinate for Dref sampling
if (m_fsKey.Stages[i].Contents.SampleDref)
sampler.texcoordCnt++;
break;
case D3DRTYPE_CUBETEXTURE:
dimensionality = spv::DimCube;

View File

@ -48,10 +48,15 @@ namespace dxvk {
struct D3D9FixedFunctionOptions {
D3D9FixedFunctionOptions(const D3D9Options* options);
bool invariantPosition;
bool forceSampleRateShading;
bool invariantPosition;
bool forceSampleRateShading;
int32_t drefScaling;
};
constexpr float GetDrefScaleFactor(int32_t bitDepth) {
return 1.0f / (float(1 << bitDepth) - 1.0f);
}
// Returns new oFog if VS
// Returns new oColor if PS
uint32_t DoFixedFunctionFog(D3D9ShaderSpecConstantManager& spec, SpirvModule& spvModule, const D3D9FogContext& fogCtx);
@ -59,7 +64,7 @@ namespace dxvk {
void DoFixedFunctionAlphaTest(SpirvModule& spvModule, const D3D9AlphaTestContext& ctx);
// Returns a render state block
uint32_t SetupRenderStateBlock(SpirvModule& spvModule, uint32_t count);
uint32_t SetupRenderStateBlock(SpirvModule& spvModule);
struct D3D9PointSizeInfoVS {
uint32_t defaultValue;
@ -128,9 +133,11 @@ namespace dxvk {
uint32_t VertexBlendCount : 3;
uint32_t VertexClipping : 1;
uint32_t Projected : 8;
} Contents;
uint32_t Primitive[4];
uint32_t Primitive[5];
};
};
@ -163,6 +170,7 @@ namespace dxvk {
uint32_t Projected : 1;
uint32_t ProjectedCount : 3;
uint32_t SampleDref : 1;
uint32_t TextureBound : 1;

View File

@ -438,9 +438,15 @@ namespace dxvk {
D3D9VkFormatTable::D3D9VkFormatTable(
const Rc<DxvkAdapter>& adapter,
const D3D9Options& options) {
m_dfSupport = options.supportDFFormats;
const auto& props = adapter->deviceProperties();
uint32_t vendorId = options.customVendorId == -1 ? props.vendorID : uint32_t(options.customVendorId);
// NVIDIA does not natively support any DF formats
m_dfSupport = vendorId == uint32_t(DxvkGpuVendor::Nvidia) ? false : options.supportDFFormats;
m_x4r4g4b4Support = options.supportX4R4G4B4;
m_d32supportFinal = options.supportD32;
// Only AMD supports D16_LOCKABLE natively
m_d16lockableSupport = vendorId == uint32_t(DxvkGpuVendor::Amd) ? true : options.supportD16Lockable;
// AMD do not support 24-bit depth buffers on Vulkan,
// so we have to fall back to a 32-bit depth format.
@ -473,15 +479,15 @@ namespace dxvk {
if (Format == D3D9Format::X4R4G4B4 && !m_x4r4g4b4Support)
return D3D9_VK_FORMAT_MAPPING();
if (Format == D3D9Format::D16_LOCKABLE && !m_d16lockableSupport)
return D3D9_VK_FORMAT_MAPPING();
if (Format == D3D9Format::DF16 && !m_dfSupport)
return D3D9_VK_FORMAT_MAPPING();
if (Format == D3D9Format::DF24 && !m_dfSupport)
return D3D9_VK_FORMAT_MAPPING();
if (Format == D3D9Format::D32 && !m_d32supportFinal)
return D3D9_VK_FORMAT_MAPPING();
if (!m_d24s8Support && mapping.FormatColor == VK_FORMAT_D24_UNORM_S8_UINT)
mapping.FormatColor = mapping.Aspect & VK_IMAGE_ASPECT_STENCIL_BIT ? VK_FORMAT_D32_SFLOAT_S8_UINT : VK_FORMAT_D32_SFLOAT;

View File

@ -217,7 +217,7 @@ namespace dxvk {
bool m_dfSupport;
bool m_x4r4g4b4Support;
bool m_d32supportFinal;
bool m_d16lockableSupport;
};
inline bool IsFourCCFormat(D3D9Format format) {
@ -230,6 +230,7 @@ namespace dxvk {
&& format != D3D9Format::MULTI2_ARGB8
&& format != D3D9Format::UYVY
&& format != D3D9Format::R8G8_B8G8
&& format != D3D9Format::YUY2
&& format != D3D9Format::G8R8_G8B8
&& format != D3D9Format::DXT1
&& format != D3D9Format::DXT2

View File

@ -134,7 +134,7 @@ namespace dxvk {
info.stage = VK_SHADER_STAGE_COMPUTE_BIT;
info.bindingCount = bindings.size();
info.bindings = bindings.data();
info.pushConstOffset = 0;
info.pushConstStages = VK_SHADER_STAGE_COMPUTE_BIT;
info.pushConstSize = sizeof(VkExtent2D);
return new DxvkShader(info, std::move(code));

View File

@ -101,9 +101,7 @@ IDirect3DDevice9On12 : public IUnknown {
virtual HRESULT STDMETHODCALLTYPE ReturnUnderlyingResource(IDirect3DResource9* resource, UINT num_sync, UINT64* signal_values, ID3D12Fence** fences) = 0;
};
#ifdef _MSC_VER
struct __declspec(uuid("e7fda234-b589-4049-940d-8878977531c8")) IDirect3DDevice9On12;
#else
#ifndef _MSC_VER
__CRT_UUID_DECL(IDirect3DDevice9On12, 0xe7fda234,0xb589,0x4049,0x94,0x0d,0x88,0x78,0x97,0x75,0x31,0xc8);
#endif

View File

@ -14,7 +14,7 @@ namespace dxvk {
Singleton<DxvkInstance> g_dxvkInstance;
D3D9InterfaceEx::D3D9InterfaceEx(bool bExtended)
: m_instance ( g_dxvkInstance.acquire() )
: m_instance ( g_dxvkInstance.acquire(DxvkInstanceFlag::ClientApiIsD3D9) )
, m_d3d8Bridge ( this )
, m_extended ( bExtended )
, m_d3d9Options ( nullptr, m_instance->config() )
@ -354,6 +354,12 @@ namespace dxvk {
|| pPresentationParameters == nullptr)
return D3DERR_INVALIDCALL;
// creating a device with D3DCREATE_PUREDEVICE only works in conjunction
// with D3DCREATE_HARDWARE_VERTEXPROCESSING on native drivers
if (BehaviorFlags & D3DCREATE_PUREDEVICE &&
!(BehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING))
return D3DERR_INVALIDCALL;
auto* adapter = GetAdapter(Adapter);
if (adapter == nullptr)

View File

@ -229,12 +229,7 @@ ID3D9VkExtSwapchain : public IUnknown {
virtual void STDMETHODCALLTYPE UnlockAdditionalFormats() = 0;
};
#ifdef _MSC_VER
struct __declspec(uuid("3461a81b-ce41-485b-b6b5-fcf08ba6a6bd")) ID3D9VkInteropInterface;
struct __declspec(uuid("d56344f5-8d35-46fd-806d-94c351b472c1")) ID3D9VkInteropTexture;
struct __declspec(uuid("2eaa4b89-0107-4bdb-87f7-0f541c493ce0")) ID3D9VkInteropDevice;
struct __declspec(uuid("13776e93-4aa9-430a-a4ec-fe9e281181d5")) ID3D9VkExtSwapchain;
#else
#ifndef _MSC_VER
__CRT_UUID_DECL(ID3D9VkInteropInterface, 0x3461a81b,0xce41,0x485b,0xb6,0xb5,0xfc,0xf0,0x8b,0xa6,0xa6,0xbd);
__CRT_UUID_DECL(ID3D9VkInteropTexture, 0xd56344f5,0x8d35,0x46fd,0x80,0x6d,0x94,0xc3,0x51,0xb4,0x72,0xc1);
__CRT_UUID_DECL(ID3D9VkInteropDevice, 0x2eaa4b89,0x0107,0x4bdb,0x87,0xf7,0x0f,0x54,0x1c,0x49,0x3c,0xe0);

View File

@ -81,7 +81,7 @@ namespace dxvk {
D3D9Memory (D3D9Memory&& other);
D3D9Memory& operator = (D3D9Memory&& other);
operator bool() const { return m_chunk != nullptr; }
explicit operator bool() const { return m_chunk != nullptr; }
void Map();
void Unmap();
@ -139,7 +139,7 @@ namespace dxvk {
D3D9Memory (D3D9Memory&& other);
D3D9Memory& operator = (D3D9Memory&& other);
operator bool() const { return m_ptr != nullptr; }
explicit operator bool() const { return m_ptr != nullptr; }
void Map() {}
void Unmap() {}

View File

@ -30,8 +30,11 @@ namespace dxvk {
return E_NOINTERFACE;
}
HRESULT STDMETHODCALLTYPE D3D9On12::ReturnUnderlyingResource(IDirect3DResource9* resource, UINT num_sync, UINT64* signal_values, ID3D12Fence** fences) {
Logger::err("D3D9On12::GetD3D12Device: ReturnUnderlyingResource: Stub");
return E_NOINTERFACE;
if (num_sync)
Logger::err("D3D9On12::GetD3D12Device: ReturnUnderlyingResource: Stub");
m_device->FlushAndSync9On12();
return S_OK;
}
}

View File

@ -37,8 +37,8 @@ namespace dxvk {
this->customDeviceId = parsePciId(config.getOption<std::string>("d3d9.customDeviceId"));
this->customDeviceDesc = config.getOption<std::string>("d3d9.customDeviceDesc");
const int32_t vendorId = this->customDeviceId != -1
? this->customDeviceId
const uint32_t vendorId = this->customVendorId != -1
? this->customVendorId
: (adapter != nullptr ? adapter->deviceProperties().vendorID : 0);
this->maxFrameLatency = config.getOption<int32_t> ("d3d9.maxFrameLatency", 0);
@ -55,12 +55,12 @@ namespace dxvk {
this->maxAvailableMemory = config.getOption<int32_t> ("d3d9.maxAvailableMemory", 4096);
this->supportDFFormats = config.getOption<bool> ("d3d9.supportDFFormats", true);
this->supportX4R4G4B4 = config.getOption<bool> ("d3d9.supportX4R4G4B4", true);
this->supportD32 = config.getOption<bool> ("d3d9.supportD32", true);
this->supportD16Lockable = config.getOption<bool> ("d3d9.supportD16Lockable", false);
this->useD32forD24 = config.getOption<bool> ("d3d9.useD32forD24", false);
this->disableA8RT = config.getOption<bool> ("d3d9.disableA8RT", false);
this->invariantPosition = config.getOption<bool> ("d3d9.invariantPosition", true);
this->memoryTrackTest = config.getOption<bool> ("d3d9.memoryTrackTest", false);
this->supportVCache = config.getOption<bool> ("d3d9.supportVCache", vendorId == 0x10de);
this->supportVCache = config.getOption<bool> ("d3d9.supportVCache", vendorId == uint32_t(DxvkGpuVendor::Nvidia));
this->enableDialogMode = config.getOption<bool> ("d3d9.enableDialogMode", false);
this->forceSamplerTypeSpecConstants = config.getOption<bool> ("d3d9.forceSamplerTypeSpecConstants", false);
this->forceSwapchainMSAA = config.getOption<int32_t> ("d3d9.forceSwapchainMSAA", -1);
@ -76,6 +76,11 @@ namespace dxvk {
this->deviceLossOnFocusLoss = config.getOption<bool> ("d3d9.deviceLossOnFocusLoss", false);
this->samplerLodBias = config.getOption<float> ("d3d9.samplerLodBias", 0.0f);
this->clampNegativeLodBias = config.getOption<bool> ("d3d9.clampNegativeLodBias", false);
this->countLosableResources = config.getOption<bool> ("d3d9.countLosableResources", true);
this->reproducibleCommandStream = config.getOption<bool> ("d3d9.reproducibleCommandStream", false);
// D3D8 options
this->drefScaling = config.getOption<int32_t> ("d3d8.scaleDref", 0);
// Clamp LOD bias so that people don't abuse this in unintended ways
this->samplerLodBias = dxvk::fclamp(this->samplerLodBias, -2.0f, 1.0f);
@ -89,8 +94,8 @@ namespace dxvk {
d3d9FloatEmulation = D3D9FloatEmulation::Enabled;
} else {
bool hasMulz = adapter != nullptr
&& (adapter->matchesDriver(VK_DRIVER_ID_MESA_RADV, 0, 0)
|| adapter->matchesDriver(VK_DRIVER_ID_MESA_NVK, 0, 0));
&& (adapter->matchesDriver(VK_DRIVER_ID_MESA_RADV)
|| adapter->matchesDriver(VK_DRIVER_ID_MESA_NVK));
d3d9FloatEmulation = hasMulz ? D3D9FloatEmulation::Strict : D3D9FloatEmulation::Enabled;
}

View File

@ -78,8 +78,8 @@ namespace dxvk {
/// Support X4R4G4B4
bool supportX4R4G4B4;
/// Support D32
bool supportD32;
/// Support D16_LOCKABLE
bool supportD16Lockable;
/// Use D32f for D24
bool useD32forD24;
@ -152,6 +152,17 @@ namespace dxvk {
/// Enable emulation of device loss when a fullscreen app loses focus
bool deviceLossOnFocusLoss;
/// Disable counting losable resources and rejecting calls to Reset() if any are still alive
bool countLosableResources;
/// Ensure that for the same D3D commands the output VK commands
/// don't change between runs. Useful for comparative benchmarking,
/// can negatively affect performance.
bool reproducibleCommandStream;
/// Enable depth texcoord Z (Dref) scaling (D3D8 quirk)
int32_t drefScaling;
};
}

View File

@ -41,11 +41,6 @@ namespace dxvk {
case D3DQUERYTYPE_TIMESTAMPFREQ:
break;
case D3DQUERYTYPE_VERTEXSTATS:
m_query[0] = dxvkDevice->createGpuQuery(
VK_QUERY_TYPE_PIPELINE_STATISTICS, 0, 0);
break;
default:
throw DxvkError(str::format("D3D9Query: Unsupported query type ", m_queryType));
}
@ -222,12 +217,10 @@ namespace dxvk {
switch (m_queryType) {
case D3DQUERYTYPE_VCACHE:
// Don't know what the hell any of this means.
// Nor do I care. This just makes games work.
m_dataCache.VCache.Pattern = MAKEFOURCC('H', 'C', 'A', 'C');
m_dataCache.VCache.Pattern = MAKEFOURCC('C', 'A', 'C', 'H');
m_dataCache.VCache.OptMethod = 1;
m_dataCache.VCache.CacheSize = 24;
m_dataCache.VCache.MagicNumber = 20;
m_dataCache.VCache.CacheSize = 16;
m_dataCache.VCache.MagicNumber = 7;
break;
case D3DQUERYTYPE_OCCLUSION:
@ -246,11 +239,6 @@ namespace dxvk {
m_dataCache.TimestampFreq = GetTimestampQueryFrequency();
break;
case D3DQUERYTYPE_VERTEXSTATS:
m_dataCache.VertexStats.NumRenderedTriangles = queryData[0].statistic.iaPrimitives;
m_dataCache.VertexStats.NumExtraClippingTriangles = queryData[0].statistic.clipPrimitives;
break;
default:
break;
}
@ -276,7 +264,6 @@ namespace dxvk {
void D3D9Query::Begin(DxvkContext* ctx) {
switch (m_queryType) {
case D3DQUERYTYPE_OCCLUSION:
case D3DQUERYTYPE_VERTEXSTATS:
ctx->beginQuery(m_query[0]);
break;
@ -296,7 +283,6 @@ namespace dxvk {
ctx->writeTimestamp(m_query[0]);
break;
case D3DQUERYTYPE_VERTEXSTATS:
case D3DQUERYTYPE_OCCLUSION:
ctx->endQuery(m_query[0]);
break;
@ -314,7 +300,6 @@ namespace dxvk {
bool D3D9Query::QueryBeginnable(D3DQUERYTYPE QueryType) {
return QueryType == D3DQUERYTYPE_OCCLUSION
|| QueryType == D3DQUERYTYPE_VERTEXSTATS
|| QueryType == D3DQUERYTYPE_TIMESTAMPDISJOINT;
}
@ -338,7 +323,6 @@ namespace dxvk {
case D3DQUERYTYPE_TIMESTAMP:
case D3DQUERYTYPE_TIMESTAMPDISJOINT:
case D3DQUERYTYPE_TIMESTAMPFREQ:
case D3DQUERYTYPE_VERTEXSTATS:
return D3D_OK;
default:

View File

@ -169,11 +169,11 @@ namespace dxvk {
constexpr D3DLIGHT9 DefaultLight = {
D3DLIGHT_DIRECTIONAL, // Type
{1.0f, 1.0f, 1.0f, 1.0f}, // Diffuse
{1.0f, 1.0f, 1.0f, 0.0f}, // Diffuse
{0.0f, 0.0f, 0.0f, 0.0f}, // Specular
{0.0f, 0.0f, 0.0f, 0.0f}, // Ambient
{0.0f, 0.0f, 0.0f}, // Position
{0.0f, 0.0f, 0.0f}, // Direction
{0.0f, 0.0f, 1.0f}, // Direction
0.0f, // Range
0.0f, // Falloff
0.0f, 0.0f, 0.0f, // Attenuations [constant, linear, quadratic]
@ -195,7 +195,7 @@ namespace dxvk {
const T* operator & () const { ensure(); return m_data.get(); }
T* operator & () { ensure(); return m_data.get(); }
operator bool() { return m_data != nullptr; }
explicit operator bool() const { return m_data != nullptr; }
operator T() { ensure(); return *m_data; }
void ensure() const { if (!m_data) m_data = std::make_unique<T>(); }
@ -213,7 +213,7 @@ namespace dxvk {
T& operator=(const T& x) { m_data = x; return m_data; }
operator bool() { return true; }
explicit operator bool() const { return true; }
operator T() { return m_data; }
const T* operator -> () const { return &m_data; }

View File

@ -18,7 +18,8 @@ namespace dxvk {
}
D3D9StateBlock::~D3D9StateBlock() {
m_parent->DecrementLosableCounter();
if (!m_parent->IsD3D8Compatible())
m_parent->DecrementLosableCounter();
}
HRESULT STDMETHODCALLTYPE D3D9StateBlock::QueryInterface(
@ -48,7 +49,7 @@ namespace dxvk {
if (m_captures.flags.test(D3D9CapturedStateFlag::VertexDecl))
SetVertexDeclaration(m_deviceState->vertexDecl.ptr());
ApplyOrCapture<D3D9StateFunction::Capture>();
ApplyOrCapture<D3D9StateFunction::Capture, true>();
return D3D_OK;
}
@ -60,7 +61,7 @@ namespace dxvk {
if (m_captures.flags.test(D3D9CapturedStateFlag::VertexDecl) && m_state.vertexDecl != nullptr)
m_parent->SetVertexDeclaration(m_state.vertexDecl.ptr());
ApplyOrCapture<D3D9StateFunction::Apply>();
ApplyOrCapture<D3D9StateFunction::Apply, false>();
m_applying = false;
return D3D_OK;
@ -121,6 +122,20 @@ namespace dxvk {
}
HRESULT D3D9StateBlock::SetStreamSourceWithoutOffset(
UINT StreamNumber,
D3D9VertexBuffer* pStreamData,
UINT Stride) {
m_state.vertexBuffers[StreamNumber].vertexBuffer = pStreamData;
m_state.vertexBuffers[StreamNumber].stride = Stride;
m_captures.flags.set(D3D9CapturedStateFlag::VertexBuffers);
m_captures.vertexBuffers.set(StreamNumber, true);
return D3D_OK;
}
HRESULT D3D9StateBlock::SetStreamSourceFreq(UINT StreamNumber, UINT Setting) {
m_state.streamFreq[StreamNumber] = Setting;
@ -571,8 +586,12 @@ namespace dxvk {
m_captures.flags.set(D3D9CapturedStateFlag::Material);
}
if (Type != D3D9StateBlockType::None)
this->Capture();
if (Type != D3D9StateBlockType::None) {
if (m_captures.flags.test(D3D9CapturedStateFlag::VertexDecl))
SetVertexDeclaration(m_deviceState->vertexDecl.ptr());
ApplyOrCapture<D3D9StateFunction::Capture, false>();
}
}
}

View File

@ -115,6 +115,11 @@ namespace dxvk {
UINT OffsetInBytes,
UINT Stride);
HRESULT SetStreamSourceWithoutOffset(
UINT StreamNumber,
D3D9VertexBuffer* pStreamData,
UINT Stride);
HRESULT SetStreamSourceFreq(UINT StreamNumber, UINT Setting);
HRESULT SetStateTexture(DWORD StateSampler, IDirect3DBaseTexture9* pTexture);
@ -181,7 +186,7 @@ namespace dxvk {
Capture
};
template <typename Dst, typename Src>
template <typename Dst, typename Src, bool IgnoreStreamOffset>
void ApplyOrCapture(Dst* dst, const Src* src) {
if (m_captures.flags.test(D3D9CapturedStateFlag::StreamFreq)) {
for (uint32_t idx : bit::BitMask(m_captures.streamFreq.dword(0)))
@ -211,11 +216,19 @@ namespace dxvk {
if (m_captures.flags.test(D3D9CapturedStateFlag::VertexBuffers)) {
for (uint32_t idx : bit::BitMask(m_captures.vertexBuffers.dword(0))) {
const auto& vbo = src->vertexBuffers[idx];
dst->SetStreamSource(
idx,
vbo.vertexBuffer.ptr(),
vbo.offset,
vbo.stride);
if constexpr (!IgnoreStreamOffset) {
dst->SetStreamSource(
idx,
vbo.vertexBuffer.ptr(),
vbo.offset,
vbo.stride);
} else {
// For whatever reason, D3D9 doesn't capture the stream offset
dst->SetStreamSourceWithoutOffset(
idx,
vbo.vertexBuffer.ptr(),
vbo.stride);
}
}
}
@ -324,12 +337,12 @@ namespace dxvk {
}
}
template <D3D9StateFunction Func>
template <D3D9StateFunction Func, bool IgnoreStreamOffset>
void ApplyOrCapture() {
if constexpr (Func == D3D9StateFunction::Apply)
ApplyOrCapture(m_parent, &m_state);
ApplyOrCapture<D3D9DeviceEx, D3D9CapturableState, IgnoreStreamOffset>(m_parent, &m_state);
else if constexpr (Func == D3D9StateFunction::Capture)
ApplyOrCapture(this, m_deviceState);
ApplyOrCapture<D3D9StateBlock, D3D9DeviceState, IgnoreStreamOffset>(this, m_deviceState);
}
template <

View File

@ -65,6 +65,16 @@ namespace dxvk {
if (this_thread::isInModuleDetachment())
return;
{
// Locking here and in Device::GetFrontBufferData
// ensures that other threads don't accidentally access a stale pointer.
D3D9DeviceLock lock = m_parent->LockDevice();
if (m_parent->GetMostRecentlyUsedSwapchain() == this) {
m_parent->ResetMostRecentlyUsedSwapchain();
}
}
DestroyBackBuffers();
ResetWindowProc(m_window);
@ -112,6 +122,8 @@ namespace dxvk {
DWORD dwFlags) {
D3D9DeviceLock lock = m_parent->LockDevice();
m_parent->SetMostRecentlyUsedSwapchain(this);
if (unlikely(m_parent->IsDeviceLost()))
return D3DERR_DEVICELOST;
@ -147,6 +159,8 @@ namespace dxvk {
bool recreate = false;
recreate |= m_wctx->presenter == nullptr;
recreate |= m_dialog != m_lastDialog;
if (options->deferSurfaceCreation)
recreate |= m_parent->IsDeviceReset();
if (m_wctx->presenter != nullptr) {
m_dirty |= m_wctx->presenter->setSyncInterval(presentInterval) != VK_SUCCESS;
@ -158,6 +172,9 @@ namespace dxvk {
m_lastDialog = m_dialog;
if (m_window == nullptr)
return D3D_OK;
#ifdef _WIN32
const bool useGDIFallback = m_partialCopy && !HasFrontBuffer();
if (useGDIFallback)
@ -177,6 +194,7 @@ namespace dxvk {
if (!m_wctx->presenter->hasSwapChain())
return D3D_OK;
UpdateTargetFrameRate(presentInterval);
PresentImage(presentInterval);
return D3D_OK;
} catch (const DxvkError& e) {
@ -380,6 +398,20 @@ namespace dxvk {
blitInfo.srcOffsets[0] = VkOffset3D{ 0, 0, 0 };
blitInfo.srcOffsets[1] = VkOffset3D{ int32_t(srcExtent.width), int32_t(srcExtent.height), 1 };
#ifdef _WIN32
if (m_presentParams.Windowed) {
// In windowed mode, GetFrontBufferData takes a screenshot of the entire screen.
// So place the copy of the front buffer at the position of the window.
POINT point = { 0, 0 };
if (ClientToScreen(m_window, &point) != 0) {
blitInfo.dstOffsets[0].x = point.x;
blitInfo.dstOffsets[0].y = point.y;
blitInfo.dstOffsets[1].x += point.x;
blitInfo.dstOffsets[1].y += point.y;
}
}
#endif
m_parent->EmitCs([
cDstImage = blittedSrc,
cDstMap = dstTexInfo->GetMapping().Swizzle,
@ -899,7 +931,6 @@ namespace dxvk {
presenterDesc.fullScreenExclusive = PickFullscreenMode();
m_wctx->presenter = new Presenter(m_device, m_wctx->frameLatencySignal, presenterDesc);
m_wctx->presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate);
}
@ -966,6 +997,9 @@ namespace dxvk {
void D3D9SwapChainEx::UpdateWindowCtx() {
if (m_window == nullptr)
return;
if (!m_presenters.count(m_window)) {
auto res = m_presenters.emplace(
std::piecewise_construct,
@ -1079,6 +1113,17 @@ namespace dxvk {
}
void D3D9SwapChainEx::UpdateTargetFrameRate(uint32_t SyncInterval) {
double frameRateOption = double(m_parent->GetOptions()->maxFrameRate);
double frameRate = std::max(frameRateOption, 0.0);
if (SyncInterval && frameRateOption == 0.0)
frameRate = -m_displayRefreshRate / double(SyncInterval);
m_wctx->presenter->setFrameRateLimit(frameRate);
}
void D3D9SwapChainEx::SyncFrameLatency() {
// Wait for the sync event so that we respect the maximum frame latency
m_wctx->frameLatencySignal->wait(m_wctx->frameId - GetActualFrameLatency());
@ -1295,10 +1340,10 @@ namespace dxvk {
|| dstRect.right - dstRect.left != LONG(width)
|| dstRect.bottom - dstRect.top != LONG(height);
bool recreate =
m_wctx->presenter == nullptr
|| m_wctx->presenter->info().imageExtent.width != width
|| m_wctx->presenter->info().imageExtent.height != height;
bool recreate = m_wctx != nullptr
&& (m_wctx->presenter == nullptr
|| m_wctx->presenter->info().imageExtent.width != width
|| m_wctx->presenter->info().imageExtent.height != height);
m_swapchainExtent = { width, height };
m_dstRect = dstRect;

View File

@ -213,6 +213,8 @@ namespace dxvk {
void InitRamp();
void UpdateTargetFrameRate(uint32_t SyncInterval);
uint32_t GetActualFrameLatency();
uint32_t PickFormats(

View File

@ -9,13 +9,14 @@ namespace dxvk {
// Doesn't compare everything, only what we use in SWVP.
size_t D3D9VertexDeclHash::operator () (const D3D9VertexElements& key) const {
size_t D3D9VertexDeclHash::operator () (const D3D9CompactVertexElements& key) const {
DxvkHashState hash;
std::hash<BYTE> bytehash;
std::hash<WORD> wordhash;
for (auto& element : key) {
for (uint32_t i = 0; i < key.size(); i++) {
const auto& element = key[i];
hash.add(wordhash(element.Stream));
hash.add(wordhash(element.Offset));
hash.add(bytehash(element.Type));
@ -27,7 +28,7 @@ namespace dxvk {
return hash;
}
bool D3D9VertexDeclEq::operator () (const D3D9VertexElements& a, const D3D9VertexElements& b) const {
bool D3D9VertexDeclEq::operator () (const D3D9CompactVertexElements& a, const D3D9CompactVertexElements& b) const {
if (a.size() != b.size())
return false;
@ -109,7 +110,7 @@ namespace dxvk {
m_module.opLabel(m_module.allocateId());
}
void compile(const D3D9VertexDecl* pDecl) {
void compile(const D3D9CompactVertexElements& elements) {
uint32_t uint_t = m_module.defIntType(32, false);
uint32_t float_t = m_module.defFloatType(32);
uint32_t vec4_t = m_module.defVectorType(float_t, 4);
@ -144,13 +145,22 @@ namespace dxvk {
uint32_t primitiveId = m_module.opLoad(uint_t, primitiveIdPtr);
// The size of any given vertex
uint32_t vertexSize = m_module.constu32(pDecl->GetSize() / sizeof(uint32_t));
uint32_t size = 0;
for (uint32_t i = 0; i < elements.size(); i++) {
const auto& element = elements[i];
if (element.Stream == 0 && element.Type != D3DDECLTYPE_UNUSED) {
size = std::max(size, element.Offset + GetDecltypeSize(D3DDECLTYPE(element.Type)));
}
}
uint32_t vertexSize = m_module.constu32(size / sizeof(uint32_t));
//The offset of this vertex from the beginning of the buffer
uint32_t thisVertexOffset = m_module.opIMul(uint_t, vertexSize, primitiveId);
for (auto& element : pDecl->GetElements()) {
for (uint32_t i = 0; i < elements.size(); i++) {
const auto& element = elements[i];
// Load the slot associated with this element
DxsoSemantic semantic = { DxsoUsage(element.Usage), element.UsageIndex };
@ -297,9 +307,7 @@ namespace dxvk {
};
Rc<DxvkShader> D3D9SWVPEmulator::GetShaderModule(D3D9DeviceEx* pDevice, const D3D9VertexDecl* pDecl) {
auto& elements = pDecl->GetElements();
Rc<DxvkShader> D3D9SWVPEmulator::GetShaderModule(D3D9DeviceEx* pDevice, D3D9CompactVertexElements&& elements) {
// Use the shader's unique key for the lookup
{ std::unique_lock<dxvk::mutex> lock(m_mutex);
@ -317,7 +325,7 @@ namespace dxvk {
// This shader has not been compiled yet, so we have to create a
// new module. This takes a while, so we won't lock the structure.
D3D9SWVPEmulatorGenerator generator(name);
generator.compile(pDecl);
generator.compile(elements);
Rc<DxvkShader> shader = generator.finalize();
shader->setShaderKey(key);
@ -338,7 +346,8 @@ namespace dxvk {
// that object instead and discard the newly created module.
{ std::unique_lock<dxvk::mutex> lock(m_mutex);
auto status = m_modules.insert({ elements, shader });
std::pair<D3D9CompactVertexElements, Rc<DxvkShader>> pair = { std::move(elements), shader };
auto status = m_modules.insert(std::move(pair));
if (!status.second)
return status.first->second;
}

Some files were not shown because too many files have changed in this diff Show More