To date, D3D12 has had several iterations of shader caching APIs. However, they’ve all been focused around caching driver compilations of app-provided bytecode. This process is known to be expensive, and has to be performed at runtime, because that’s the only place that the driver and hardware are known. The expectation is that the app has compiled its shaders into bytecode during some offline step, so only that last bit is the thing that needs to be cached.
As it turns out, this is not always the case. While we discourage apps from needlessly invoking the shader compiler at runtime, there are cases where apps may need to do some dynamic work that would benefit from caching:
For several years now, Windows 10 has included a component called D3DSCache, which is a DirectX Shader Cache. This component manages caches of key/value pairs which are:
These caches are currently used for two purposes:
This document outlines a new third use for this component: to enable applications to cache DXBC, DXIL, or other intermediate shader code representations, tied to some opaque key. For example, an app might have an input pre-compiled shader, combined with some set of constants, which map to a specialized variation of that shader. The first time the app sees a specific combination of (shader + constants), i.e. the key, they would perform the specialization and cache the result. The next time they see it, they can look it up from the cache.
In addition to the ability for applications to cache their own shaders and intermediates, we’ve heard feedback that apps want more control over the existing caches, for profiling purposes. To that extent, we’re adding APIs to enable apps to clear existing caches, as well as disable them to prevent new entries from being added. We’re also going to coordinate with IHVs, and provide suggestions that they do the same for any caches which are not under OS control.
D3D12_SHADER_CACHE_FLAG_NONE = 0x0,
D3D12_SHADER_CACHE_FLAG_DRIVER_VERSIONED = 0x1,
D3D12_SHADER_CACHE_FLAG_USE_WORKING_DIR = 0x2,
interface ID3D12ShaderCacheSession : ID3D12DeviceChild
_In_reads_bytes_(KeySize) const void* pKey,
_Out_writes_bytes_(*pValueSize) void* pValue,
_Inout_ UINT* pValueSize);
_In_reads_bytes_(KeySize) const void* pKey,
_In_reads_bytes_(ValueSize) const void* pValue,
D3D12_SHADER_CACHE_KIND_FLAG_IMPLICIT_D3D_CACHE_FOR_DRIVER = 0x1,
D3D12_SHADER_CACHE_KIND_FLAG_IMPLICIT_D3D_CONVERSIONS = 0x2,
D3D12_SHADER_CACHE_KIND_FLAG_IMPLICIT_DRIVER_MANAGED = 0x4,
D3D12_SHADER_CACHE_KIND_FLAG_APPLICATION_MANAGED = 0x8,
D3D12_SHADER_CACHE_CONTROL_FLAG_DISABLE = 0x1,
D3D12_SHADER_CACHE_CONTROL_FLAG_ENABLE = 0x2,
D3D12_SHADER_CACHE_CONTROL_FLAG_CLEAR = 0x4,
interface ID3D12Device9 : ID3D12Device8
_In_ const D3D12_SHADER_CACHE_SESSION_DESC* pDesc,
_COM_Outptr_opt_ void** ppvSession);
|There is no backing file for this cache. All stores are discarded when the session object is destroyed.
|The session is backed by files on disk, that will persist from run to run, unless cleared.
Disk caches can be cleared in one of these ways:
SetDeleteOnDestroy() on the session object, and then releasing the session.
*.lock) stored on disk for D3D12_SHADER_CACHE_FLAG_USE_WORKING_DIR caches. The application should not attempt to do this for caches stored outside of the working directory.
|The cache is implicitly versioned by the driver being used. Caches created this way are stored side-by-side for each adapter on which the application runs for multi-GPU systems. The Version field in the cache description is used as an additional constraint.
|By default, caches are stored in temporary storage, and can be cleared by disk cleanup. When this flag is used (not valid for UWP apps), the cache is instead stored in the current working directory.
|A unique identifier for this specific cache. Caches with different identifiers are stored side-by-side. Caches with the same identifier are shared across all sessions in the same process. Creating a disk cache with the same identifier as an already-existing cache will open that cache, unless the Version mismatches. In that case, if there are no other sessions open to that cache, it is cleared and re-created. If there are existing sessions, CreateShaderCacheSession returns
|Determines what kind of cache to create/open.
|Modifies behavior of the cache.
|For in-memory caches, this is the only storage available. For disk caches, all entries that are stored or found are temporarily stored in memory, until evicted by newer entries. This value determines the size of that temporary storage. Defaults to 1KB
|Controls how many entries can be stored in memory. Defaults to 128
|For disk caches, controls the maximum file size. Defaults to 128MB.
|This can be used to implicitly clear caches when an application or component update is done. If the version does not match the version stored in the cache, it will be wiped and re-created.
|Looks up an entry in the cache whose key exactly matches the provided key. Intended usage is to call twice, the first time to figure out the size, and the second to retrieve the data. This is fast due to the in-memory temporary storage. If there is an entry with the same hash as the provided key, but the key does not exactly match, returns
DXGI_ERROR_CACHE_HASH_COLLISION. If the entry is not present, returns
|Adds an entry to the cache. If there is an entry with the same key, returns
DXGI_ERROR_ALREADY_EXISTS. If there an entry with the same hash as the provided key, but the key does not match, returns
DXGI_ERROR_CACHE_HASH_COLLISION. If adding this entry would cause the cache to become larger than its maximum size, returns
|When all cache session objects corresponding to a given cache are destroyed, the cache is cleared.
|Returns the description used to create the cache session.
|Refers to the cache which is managed by D3D12 to store driver compilations of application shaders.
|Refers to the cache which is used to store D3D12’s conversions of one shader type to another (e.g. DXBC shaders to DXIL shaders).
|Refers to the cache which is managed by the driver. Operations for this cache are hints.
|Refers to all shader cache sessions created by the
CreateShaderCacheSession API. Requests to
CLEAR with this flag will apply to all currently-active application cache sessions, as well as on-disk caches created without
Any one of these caches may or may not exist.
|The cache should not be used to look up data, and should not have new data stored in it. Attempts to use/create a cache while disabled will return
|Resumes use of the cache.
|Any existing contents of the cache should be deleted.
The app cannot pass both DISABLE and ENABLE at the same time, and must pass at least one flag.
|Creates an object which grants access to a shader cache, potentially opening an existing cache or creating a new one.
|Modifies behavior of caches used internally by D3D or the driver. This API may only be used in developer mode.
The only changes here will be to hook up the control API for
DRIVER_MANAGED cache kinds to the driver. Additionally, it seems worth improving the existing
D3D12_SHADER_CACHE_SUPPORT_FLAGS (queryable via
D3D12_FEATURE_SHADER_CACHE) to add a new flag indicating the presence of a driver cache:
D3D12_SHADER_CACHE_SUPPORT_NONE = 0x0,
D3D12_SHADER_CACHE_SUPPORT_SINGLE_PSO = 0x1, // Always supported
D3D12_SHADER_CACHE_SUPPORT_LIBRARY = 0x2,
D3D12_SHADER_CACHE_SUPPORT_AUTOMATIC_INPROC_CACHE = 0x4,
D3D12_SHADER_CACHE_SUPPORT_AUTOMATIC_DISK_CACHE = 0x8,
+ D3D12_SHADER_CACHE_SUPPORT_DRIVER_MANAGED_CACHE = 0x10,
To that end, we’re adding the following DDIs:
If the driver reports true for
DriverManagedShaderCachePresent, then the driver should also provide a non-null function for:
typedef HRESULT ( APIENTRY* PFND3D12DDI_IMPLICITSHADERCACHECONTROL_008n )(
D3D12DDI_HDEVICE, D3D12DDI_IMPLICIT_SHADER_CACHE_CONTROL_FLAGS_008n );
The runtime will call
pfnImplicitShaderCacheControl in response to application requests to
ID3D12Device9::ImplicitShaderCacheControl for the
D3D12_IMPLICIT_SHADER_CACHE_KIND_FLAG_DRIVER_MANAGED kind. As called out above, this API will only be supported in developer mode.