1
Fork 0
mirror of https://github.com/redstrate/dxbc.git synced 2025-04-20 03:37:47 +00:00

Add initial files

This commit is contained in:
Joshua Goins 2023-09-22 15:54:45 -04:00
commit 584a7c6533
84 changed files with 24108 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.directory
*build*/
.idea/

48
CMakeLists.txt Normal file
View file

@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.27)
project(dxbc LANGUAGES CXX)
add_library(windows-headers INTERFACE)
target_include_directories(windows-headers INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/windows)
find_package(Vulkan REQUIRED)
add_subdirectory(src/util)
add_subdirectory(src/spirv)
add_library(dxbc STATIC)
target_sources(dxbc PRIVATE
src/dxbc/dxbc_analysis.cpp
src/dxbc/dxbc_analysis.h
src/dxbc/dxbc_chunk_isgn.cpp
src/dxbc/dxbc_chunk_isgn.h
src/dxbc/dxbc_chunk_shex.cpp
src/dxbc/dxbc_chunk_shex.h
src/dxbc/dxbc_common.cpp
src/dxbc/dxbc_common.h
src/dxbc/dxbc_compiler.cpp
src/dxbc/dxbc_compiler.h
src/dxbc/dxbc_decoder.cpp
src/dxbc/dxbc_decoder.h
src/dxbc/dxbc_defs.cpp
src/dxbc/dxbc_defs.h
src/dxbc/dxbc_enums.h
src/dxbc/dxbc_header.cpp
src/dxbc/dxbc_header.h
src/dxbc/dxbc_include.h
src/dxbc/dxbc_modinfo.h
src/dxbc/dxbc_module.cpp
src/dxbc/dxbc_module.h
src/dxbc/dxbc_names.cpp
src/dxbc/dxbc_names.h
src/dxbc/dxbc_options.cpp
src/dxbc/dxbc_options.h
src/dxbc/dxbc_reader.cpp
src/dxbc/dxbc_reader.h
src/dxbc/dxbc_tag.h
src/dxbc/dxbc_util.cpp
src/dxbc/dxbc_util.h
)
target_include_directories(dxbc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(dxbc PUBLIC dxbc-util dxbc-spirv Vulkan::Vulkan)
add_subdirectory(example)

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2017 Philip Rebohle
Copyright (c) 2019 Joshua Ashton
zlib/libpng license
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
This notice may not be removed or altered from any source distribution.

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# dxbc
This is the `dxbc` decompiler pulled directly from the [DXVK source tree](https://github.com/doitsujin/dxvk). This is for my personal use, do not expect me to keep updating this.
See an example of how to use the library in `example/`.
All credit goes to the DXVK developers, of course.

3
example/CMakeLists.txt Normal file
View file

@ -0,0 +1,3 @@
add_executable(dxbc-example)
target_sources(dxbc-example PRIVATE main.cpp)
target_link_libraries(dxbc-example PRIVATE dxbc spirv-cross-core spirv-cross-glsl)

35
example/main.cpp Normal file
View file

@ -0,0 +1,35 @@
#include <iostream>
#include <dxbc/dxbc_module.h>
#include <util/log/log.h>
#include <spirv_cross/spirv_glsl.hpp>
dxvk::Logger dxvk::Logger::s_instance("dxbc.log");
int main(int argc, char *argv[]) {
// pass in the DXVK binary you want to decompile
const std::string inputFile = argv[1];
std::ifstream infile(inputFile, std::ios_base::binary);
std::vector<char> buffer((std::istreambuf_iterator<char>(infile)),
std::istreambuf_iterator<char>());
dxvk::DxbcReader reader(buffer.data(), buffer.size());
dxvk::DxbcModule module(reader);
dxvk::DxbcModuleInfo info;
auto result = module.compile(info, "test");
spirv_cross::CompilerGLSL glsl(result.code.data(), result.code.dwords());
spirv_cross::CompilerGLSL::Options options;
options.version = 310;
options.es = true;
glsl.set_common_options(options);
std::cout << glsl.compile() << std::endl;
return 0;
}

3
include/windows/oaidl.h Normal file
View file

@ -0,0 +1,3 @@
#pragma once
// Don't care.

View file

@ -0,0 +1,3 @@
#pragma once
// Don't care.

3
include/windows/ocidl.h Normal file
View file

@ -0,0 +1,3 @@
#pragma once
// Don't care.

3
include/windows/ole2.h Normal file
View file

@ -0,0 +1,3 @@
#pragma once
// Don't care.

View file

@ -0,0 +1,8 @@
/**
* This file has no copyright assigned and is placed in the Public Domain.
* This file is part of the mingw-w64 runtime package.
* No warranty is given; refer to the file DISCLAIMER.PD within this package.
*/
#if !(defined(lint) || defined(RC_INVOKED))
#pragma pack(pop)
#endif

View file

@ -0,0 +1,8 @@
/**
* This file has no copyright assigned and is placed in the Public Domain.
* This file is part of the mingw-w64 runtime package.
* No warranty is given; refer to the file DISCLAIMER.PD within this package.
*/
#if !(defined(lint) || defined(RC_INVOKED))
#pragma pack(push,4)
#endif

3
include/windows/rpc.h Normal file
View file

@ -0,0 +1,3 @@
#pragma once
// Don't care.

3
include/windows/rpcndr.h Normal file
View file

@ -0,0 +1,3 @@
#pragma once
// Don't care.

52
include/windows/unknwn.h Normal file
View file

@ -0,0 +1,52 @@
#pragma once
#include "windows_base.h"
typedef interface IUnknown IUnknown;
DEFINE_GUID(IID_IUnknown, 0x00000000,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46)
#ifdef __cplusplus
struct IUnknown {
public:
virtual HRESULT QueryInterface(REFIID riid, void** ppvObject) = 0;
template<class Q>
HRESULT STDMETHODCALLTYPE QueryInterface(Q **pp) {
return QueryInterface(__uuidof(Q), (void **)pp);
}
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};
#else
typedef struct IUnknownVtbl
{
BEGIN_INTERFACE
HRESULT (STDMETHODCALLTYPE *QueryInterface)(
IUnknown *This,
REFIID riid,
void **ppvObject
);
ULONG (STDMETHODCALLTYPE *AddRef)(IUnknown *This);
ULONG (STDMETHODCALLTYPE *Release)(IUnknown *This);
END_INTERFACE
} IUnknownVtbl;
interface IUnknown
{
CONST_VTBL struct IUnknownVtbl *lpVtbl;
};
#define IUnknown_AddRef(This) ((This)->lpVtbl->AddRef(This))
#define IUnknown_Release(This) ((This)->lpVtbl->Release(This))
#endif // __cplusplus
DECLARE_UUIDOF_HELPER(IUnknown, 0x00000000,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46)
#define IID_PPV_ARGS(ppType) __uuidof(decltype(**(ppType))), [](auto** pp) { (void)static_cast<IUnknown*>(*pp); return reinterpret_cast<void**>(pp); }(ppType)

View file

@ -0,0 +1,4 @@
#pragma once
#include "windows_base.h"
#include "unknwn.h"

View file

@ -0,0 +1,389 @@
#pragma once
#ifdef __cplusplus
#include <cstdint>
#include <cstring>
#else
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#endif // __cplusplus
// GCC complains about the COM interfaces
// not having virtual destructors
// and class conversion for C...DESC helper types
#if defined(__GNUC__) && defined(__cplusplus)
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#pragma GCC diagnostic ignored "-Wclass-conversion"
#endif // __GNUC__ && __cplusplus
typedef int32_t INT;
typedef uint32_t UINT;
typedef int32_t LONG;
typedef uint32_t ULONG;
typedef int32_t *LPLONG;
typedef int32_t HRESULT;
typedef wchar_t WCHAR;
typedef WCHAR *NWPSTR, *LPWSTR, *PWSTR;
typedef unsigned char UCHAR, *PUCHAR;
typedef char CHAR;
typedef const CHAR *LPCSTR, *PCSTR;
typedef INT BOOL;
typedef BOOL WINBOOL;
typedef uint16_t UINT16;
typedef uint32_t UINT32;
typedef uint64_t UINT64;
typedef void VOID;
typedef void* PVOID;
typedef void* LPVOID;
typedef const void* LPCVOID;
typedef size_t SIZE_T;
typedef int8_t INT8;
typedef uint8_t UINT8;
typedef uint8_t BYTE;
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;
#ifndef GUID_DEFINED
#define GUID_DEFINED
typedef struct GUID {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} GUID;
#endif // GUID_DEFINED
typedef GUID UUID;
typedef GUID IID;
#ifdef __cplusplus
#define REFIID const IID&
#define REFGUID const GUID&
#define REFCLSID const GUID&
#else
#define REFIID const IID*
#define REFGUID const GUID*
#define REFCLSID const GUID* const
#endif // __cplusplus
#ifdef __cplusplus
template <typename T>
constexpr GUID __uuidof_helper();
#define __uuidof(T) __uuidof_helper<T>()
#define __uuidof_var(T) __uuidof_helper<decltype(T)>()
inline bool operator==(const GUID& a, const GUID& b) { return std::memcmp(&a, &b, sizeof(GUID)) == 0; }
inline bool operator!=(const GUID& a, const GUID& b) { return std::memcmp(&a, &b, sizeof(GUID)) != 0; }
#endif // __cplusplus
typedef uint32_t DWORD;
typedef uint16_t WORD;
typedef DWORD *LPDWORD;
typedef void* HANDLE;
typedef HANDLE HMONITOR;
typedef HANDLE HDC;
typedef HANDLE HMODULE;
typedef HANDLE HINSTANCE;
typedef HANDLE HWND;
typedef HANDLE HKEY;
typedef HANDLE *LPHANDLE;
typedef DWORD COLORREF;
#if INTPTR_MAX == INT64_MAX
typedef int64_t INT_PTR;
typedef uint64_t UINT_PTR;
#else
typedef int32_t INT_PTR;
typedef uint32_t UINT_PTR;
#endif
typedef INT_PTR* PINT_PTR;
typedef UINT_PTR* PUINT_PTR;
#ifdef STRICT
#define DECLARE_HANDLE(a) typedef struct a##__ { int unused; } *a
#else /*STRICT*/
#define DECLARE_HANDLE(a) typedef HANDLE a
#endif /*STRICT*/
typedef char* LPSTR;
typedef wchar_t* LPWSTR;
typedef const char* LPCSTR;
typedef const wchar_t* LPCWSTR;
typedef struct LUID {
DWORD LowPart;
LONG HighPart;
} LUID;
typedef struct POINT {
LONG x;
LONG y;
} POINT;
typedef POINT* LPPOINT;
typedef struct RECT {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT,*PRECT,*NPRECT,*LPRECT;
typedef struct SIZE {
LONG cx;
LONG cy;
} SIZE,*PSIZE,*LPSIZE;
typedef union {
struct {
DWORD LowPart;
LONG HighPart;
};
struct {
DWORD LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER;
typedef struct MEMORYSTATUS {
DWORD dwLength;
SIZE_T dwTotalPhys;
} MEMORYSTATUS;
typedef struct SECURITY_ATTRIBUTES {
DWORD nLength;
void* lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;
typedef struct PALETTEENTRY {
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags;
} PALETTEENTRY, *PPALETTEENTRY, *LPPALETTEENTRY;
typedef struct RGNDATAHEADER {
DWORD dwSize;
DWORD iType;
DWORD nCount;
DWORD nRgnSize;
RECT rcBound;
} RGNDATAHEADER;
typedef struct RGNDATA {
RGNDATAHEADER rdh;
char Buffer[1];
} RGNDATA,*PRGNDATA,*NPRGNDATA,*LPRGNDATA;
// Ignore these.
#define STDMETHODCALLTYPE
#define __stdcall
#define CONST const
#define CONST_VTBL const
#define TRUE 1
#define FALSE 0
#define WAIT_TIMEOUT 0x00000102
#define WAIT_FAILED 0xffffffff
#define WAIT_OBJECT_0 0
#define WAIT_ABANDONED 0x00000080
#define interface struct
#define MIDL_INTERFACE(x) struct
#ifdef __cplusplus
#define DEFINE_GUID(iid, a, b, c, d, e, f, g, h, i, j, k) \
constexpr GUID iid = {a,b,c,{d,e,f,g,h,i,j,k}};
#define DECLARE_UUIDOF_HELPER(type, a, b, c, d, e, f, g, h, i, j, k) \
extern "C++" { template <> constexpr GUID __uuidof_helper<type>() { return GUID{a,b,c,{d,e,f,g,h,i,j,k}}; } } \
extern "C++" { template <> constexpr GUID __uuidof_helper<type*>() { return __uuidof_helper<type>(); } } \
extern "C++" { template <> constexpr GUID __uuidof_helper<const type*>() { return __uuidof_helper<type>(); } } \
extern "C++" { template <> constexpr GUID __uuidof_helper<type&>() { return __uuidof_helper<type>(); } } \
extern "C++" { template <> constexpr GUID __uuidof_helper<const type&>() { return __uuidof_helper<type>(); } }
#else
#define DEFINE_GUID(iid, a, b, c, d, e, f, g, h, i, j, k) \
static const GUID iid = {a,b,c,{d,e,f,g,h,i,j,k}};
#define DECLARE_UUIDOF_HELPER(type, a, b, c, d, e, f, g, h, i, j, k)
#endif // __cplusplus
#define __CRT_UUID_DECL(type, a, b, c, d, e, f, g, h, i, j, k) DECLARE_UUIDOF_HELPER(type, a, b, c, d, e, f, g, h, i, j, k)
#define S_OK 0
#define S_FALSE 1
#define E_INVALIDARG ((HRESULT)0x80070057)
#define E_FAIL ((HRESULT)0x80004005)
#define E_NOINTERFACE ((HRESULT)0x80004002)
#define E_NOTIMPL ((HRESULT)0x80004001)
#define E_OUTOFMEMORY ((HRESULT)0x8007000E)
#define E_POINTER ((HRESULT)0x80004003)
#define DXGI_STATUS_OCCLUDED ((HRESULT)0x087a0001)
#define DXGI_STATUS_CLIPPED ((HRESULT)0x087a0002)
#define DXGI_STATUS_NO_REDIRECTION ((HRESULT)0x087a0004)
#define DXGI_STATUS_NO_DESKTOP_ACCESS ((HRESULT)0x087a0005)
#define DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE ((HRESULT)0x087a0006)
#define DXGI_STATUS_MODE_CHANGED ((HRESULT)0x087a0007)
#define DXGI_STATUS_MODE_CHANGE_IN_PROGRESS ((HRESULT)0x087a0008)
#define DXGI_STATUS_UNOCCLUDED ((HRESULT)0x087a0009)
#define DXGI_STATUS_DDA_WAS_STILL_DRAWING ((HRESULT)0x087a000a)
#define DXGI_STATUS_PRESENT_REQUIRED ((HRESULT)0x087a002f)
#define DXGI_ERROR_INVALID_CALL ((HRESULT)0x887A0001)
#define DXGI_ERROR_NOT_FOUND ((HRESULT)0x887A0002)
#define DXGI_ERROR_MORE_DATA ((HRESULT)0x887A0003)
#define DXGI_ERROR_UNSUPPORTED ((HRESULT)0x887A0004)
#define DXGI_ERROR_DEVICE_REMOVED ((HRESULT)0x887A0005)
#define DXGI_ERROR_DEVICE_HUNG ((HRESULT)0x887A0006)
#define DXGI_ERROR_DEVICE_RESET ((HRESULT)0x887A0007)
#define DXGI_ERROR_WAS_STILL_DRAWING ((HRESULT)0x887A000A)
#define DXGI_ERROR_FRAME_STATISTICS_DISJOINT ((HRESULT)0x887A000B)
#define DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE ((HRESULT)0x887A000C)
#define DXGI_ERROR_DRIVER_INTERNAL_ERROR ((HRESULT)0x887A0020)
#define DXGI_ERROR_NONEXCLUSIVE ((HRESULT)0x887A0021)
#define DXGI_ERROR_NOT_CURRENTLY_AVAILABLE ((HRESULT)0x887A0022)
#define DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED ((HRESULT)0x887A0023)
#define DXGI_ERROR_REMOTE_OUTOFMEMORY ((HRESULT)0x887A0024)
#define DXGI_ERROR_ACCESS_LOST ((HRESULT)0x887A0026)
#define DXGI_ERROR_WAIT_TIMEOUT ((HRESULT)0x887A0027)
#define DXGI_ERROR_SESSION_DISCONNECTED ((HRESULT)0x887A0028)
#define DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE ((HRESULT)0x887A0029)
#define DXGI_ERROR_CANNOT_PROTECT_CONTENT ((HRESULT)0x887A002A)
#define DXGI_ERROR_ACCESS_DENIED ((HRESULT)0x887A002B)
#define DXGI_ERROR_NAME_ALREADY_EXISTS ((HRESULT)0x887A002C)
#define DXGI_ERROR_SDK_COMPONENT_MISSING ((HRESULT)0x887A002D)
#define WINAPI
#define WINUSERAPI
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
#define MAKE_HRESULT(sev,fac,code) \
((HRESULT) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code))) )
#ifdef __cplusplus
#define STDMETHOD(name) virtual HRESULT name
#define STDMETHOD_(type, name) virtual type name
#else
#define STDMETHOD(name) HRESULT (STDMETHODCALLTYPE *name)
#define STDMETHOD_(type, name) type (STDMETHODCALLTYPE *name)
#endif // __cplusplus
#define THIS_
#define THIS
#define __C89_NAMELESSSTRUCTNAME
#define __C89_NAMELESSUNIONNAME
#define __C89_NAMELESSUNIONNAME1
#define __C89_NAMELESSUNIONNAME2
#define __C89_NAMELESSUNIONNAME3
#define __C89_NAMELESSUNIONNAME4
#define __C89_NAMELESSUNIONNAME5
#define __C89_NAMELESSUNIONNAME6
#define __C89_NAMELESSUNIONNAME7
#define __C89_NAMELESSUNIONNAME8
#define __C89_NAMELESS
#define DUMMYUNIONNAME
#define DUMMYSTRUCTNAME
#define DUMMYUNIONNAME1
#define DUMMYUNIONNAME2
#define DUMMYUNIONNAME3
#define DUMMYUNIONNAME4
#define DUMMYUNIONNAME5
#define DUMMYUNIONNAME6
#define DUMMYUNIONNAME7
#define DUMMYUNIONNAME8
#define DUMMYUNIONNAME9
#ifdef __cplusplus
#define DECLARE_INTERFACE(x) struct x
#define DECLARE_INTERFACE_(x, y) struct x : public y
#else
#define DECLARE_INTERFACE(x) \
typedef interface x { \
const struct x##Vtbl *lpVtbl; \
} x; \
typedef const struct x##Vtbl x##Vtbl; \
const struct x##Vtbl
#define DECLARE_INTERFACE_(x, y) DECLARE_INTERFACE(x)
#endif // __cplusplus
#define BEGIN_INTERFACE
#define END_INTERFACE
#ifdef __cplusplus
#define PURE = 0
#else
#define PURE
#endif // __cplusplus
#define DECLSPEC_SELECTANY
#define __MSABI_LONG(x) x
#define ENUM_CURRENT_SETTINGS ((DWORD)-1)
#define ENUM_REGISTRY_SETTINGS ((DWORD)-2)
#define INVALID_HANDLE_VALUE ((HANDLE)-1)
#define DUPLICATE_CLOSE_SOURCE ((DWORD)0x1)
#define DUPLICATE_SAME_ACCESS ((DWORD)0x2)
#define FAILED(hr) ((HRESULT)(hr) < 0)
#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)
#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))
#define ZeroMemory RtlZeroMemory
#ifndef DEFINE_ENUM_FLAG_OPERATORS
#ifdef __cplusplus
# define DEFINE_ENUM_FLAG_OPERATORS(type) \
extern "C++" \
{ \
inline type operator &(type x, type y) { return (type)((int)x & (int)y); } \
inline type operator &=(type &x, type y) { return (type &)((int &)x &= (int)y); } \
inline type operator ~(type x) { return (type)~(int)x; } \
inline type operator |(type x, type y) { return (type)((int)x | (int)y); } \
inline type operator |=(type &x, type y) { return (type &)((int &)x |= (int)y); } \
inline type operator ^(type x, type y) { return (type)((int)x ^ (int)y); } \
inline type operator ^=(type &x, type y) { return (type &)((int &)x ^= (int)y); } \
}
#else
# define DEFINE_ENUM_FLAG_OPERATORS(type)
#endif
#endif /* DEFINE_ENUM_FLAG_OPERATORS */

115
src/dxbc/dxbc_analysis.cpp Normal file
View file

@ -0,0 +1,115 @@
#include "dxbc_analysis.h"
namespace dxvk {
DxbcAnalyzer::DxbcAnalyzer(
const DxbcModuleInfo& moduleInfo,
const DxbcProgramInfo& programInfo,
const Rc<DxbcIsgn>& isgn,
const Rc<DxbcIsgn>& osgn,
const Rc<DxbcIsgn>& psgn,
DxbcAnalysisInfo& analysis)
: m_isgn (isgn),
m_osgn (osgn),
m_psgn (psgn),
m_analysis(&analysis) {
// Get number of clipping and culling planes from the
// input and output signatures. We will need this to
// declare the shader input and output interfaces.
m_analysis->clipCullIn = getClipCullInfo(m_isgn);
m_analysis->clipCullOut = getClipCullInfo(m_osgn);
}
DxbcAnalyzer::~DxbcAnalyzer() {
}
void DxbcAnalyzer::processInstruction(const DxbcShaderInstruction& ins) {
switch (ins.opClass) {
case DxbcInstClass::Atomic: {
const uint32_t operandId = ins.dstCount - 1;
if (ins.dst[operandId].type == DxbcOperandType::UnorderedAccessView) {
const uint32_t registerId = ins.dst[operandId].idx[0].offset;
m_analysis->uavInfos[registerId].accessAtomicOp = true;
m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
}
} break;
case DxbcInstClass::TextureSample:
case DxbcInstClass::TextureGather:
case DxbcInstClass::TextureQueryLod:
case DxbcInstClass::VectorDeriv: {
m_analysis->usesDerivatives = true;
} break;
case DxbcInstClass::ControlFlow: {
if (ins.op == DxbcOpcode::Discard)
m_analysis->usesKill = true;
} break;
case DxbcInstClass::BufferLoad: {
uint32_t operandId = ins.op == DxbcOpcode::LdStructured ? 2 : 1;
bool sparseFeedback = ins.dstCount == 2;
if (ins.src[operandId].type == DxbcOperandType::UnorderedAccessView) {
const uint32_t registerId = ins.src[operandId].idx[0].offset;
m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT;
m_analysis->uavInfos[registerId].sparseFeedback |= sparseFeedback;
} else if (ins.src[operandId].type == DxbcOperandType::Resource) {
const uint32_t registerId = ins.src[operandId].idx[0].offset;
m_analysis->srvInfos[registerId].sparseFeedback |= sparseFeedback;
}
} break;
case DxbcInstClass::BufferStore: {
if (ins.dst[0].type == DxbcOperandType::UnorderedAccessView) {
const uint32_t registerId = ins.dst[0].idx[0].offset;
m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_WRITE_BIT;
}
} break;
case DxbcInstClass::TypedUavLoad: {
const uint32_t registerId = ins.src[1].idx[0].offset;
m_analysis->uavInfos[registerId].accessTypedLoad = true;
m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT;
} break;
case DxbcInstClass::TypedUavStore: {
const uint32_t registerId = ins.dst[0].idx[0].offset;
m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_WRITE_BIT;
} break;
default:
break;
}
for (uint32_t i = 0; i < ins.dstCount; i++) {
if (ins.dst[0].type == DxbcOperandType::IndexableTemp) {
uint32_t index = ins.dst[0].idx[0].offset;
m_analysis->xRegMasks[index] |= ins.dst[0].mask;
}
}
}
DxbcClipCullInfo DxbcAnalyzer::getClipCullInfo(const Rc<DxbcIsgn>& sgn) const {
DxbcClipCullInfo result;
if (sgn != nullptr) {
for (auto e = sgn->begin(); e != sgn->end(); e++) {
const uint32_t componentCount = e->componentMask.popCount();
if (e->systemValue == DxbcSystemValue::ClipDistance)
result.numClipPlanes += componentCount;
if (e->systemValue == DxbcSystemValue::CullDistance)
result.numCullPlanes += componentCount;
}
}
return result;
}
}

100
src/dxbc/dxbc_analysis.h Normal file
View file

@ -0,0 +1,100 @@
#pragma once
#include "dxbc_chunk_isgn.h"
#include "dxbc_decoder.h"
#include "dxbc_defs.h"
#include "dxbc_names.h"
#include "dxbc_modinfo.h"
#include "dxbc_util.h"
namespace dxvk {
/**
* \brief Info about unordered access views
*
* Stores whether an UAV is accessed with typed
* read or atomic instructions. This information
* will be used to generate image types.
*/
struct DxbcUavInfo {
bool accessTypedLoad = false;
bool accessAtomicOp = false;
bool sparseFeedback = false;
VkAccessFlags accessFlags = 0;
};
/**
* \brief Info about shader resource views
*
* Stores whether an SRV is accessed with
* sparse feedback. Useful for buffers.
*/
struct DxbcSrvInfo {
bool sparseFeedback = false;
};
/**
* \brief Counts cull and clip distances
*/
struct DxbcClipCullInfo {
uint32_t numClipPlanes = 0;
uint32_t numCullPlanes = 0;
};
/**
* \brief Shader analysis info
*/
struct DxbcAnalysisInfo {
std::array<DxbcUavInfo, 64> uavInfos;
std::array<DxbcSrvInfo, 128> srvInfos;
std::array<DxbcRegMask, 4096> xRegMasks;
DxbcClipCullInfo clipCullIn;
DxbcClipCullInfo clipCullOut;
bool usesDerivatives = false;
bool usesKill = false;
};
/**
* \brief DXBC shader analysis pass
*
* Collects information about the shader itself
* and the resources used by the shader, which
* will later be used by the actual compiler.
*/
class DxbcAnalyzer {
public:
DxbcAnalyzer(
const DxbcModuleInfo& moduleInfo,
const DxbcProgramInfo& programInfo,
const Rc<DxbcIsgn>& isgn,
const Rc<DxbcIsgn>& osgn,
const Rc<DxbcIsgn>& psgn,
DxbcAnalysisInfo& analysis);
~DxbcAnalyzer();
/**
* \brief Processes a single instruction
* \param [in] ins The instruction
*/
void processInstruction(
const DxbcShaderInstruction& ins);
private:
Rc<DxbcIsgn> m_isgn;
Rc<DxbcIsgn> m_osgn;
Rc<DxbcIsgn> m_psgn;
DxbcAnalysisInfo* m_analysis = nullptr;
DxbcClipCullInfo getClipCullInfo(
const Rc<DxbcIsgn>& sgn) const;
};
}

View file

@ -0,0 +1,122 @@
#include "dxbc_chunk_isgn.h"
namespace dxvk {
DxbcIsgn::DxbcIsgn(DxbcReader reader, DxbcTag tag) {
uint32_t elementCount = reader.readu32();
reader.skip(sizeof(uint32_t));
std::array<DxbcScalarType, 4> componentTypes = {
DxbcScalarType::Uint32, DxbcScalarType::Uint32,
DxbcScalarType::Sint32, DxbcScalarType::Float32,
};
// https://github.com/DarkStarSword/3d-fixes/blob/master/dx11shaderanalyse.py#L101
bool hasStream = (tag == "ISG1") || (tag == "OSG1") || (tag == "PSG1") || (tag == "OSG5");
bool hasPrecision = (tag == "ISG1") || (tag == "OSG1") || (tag == "PSG1");
for (uint32_t i = 0; i < elementCount; i++) {
DxbcSgnEntry entry;
entry.streamId = hasStream ? reader.readu32() : 0;
entry.semanticName = reader.clone(reader.readu32()).readString();
entry.semanticIndex = reader.readu32();
entry.systemValue = static_cast<DxbcSystemValue>(reader.readu32());
entry.componentType = componentTypes.at(reader.readu32());
entry.registerId = reader.readu32();
entry.componentMask = bit::extract(reader.readu32(), 0, 3);
if (hasPrecision)
reader.readu32();
m_entries.push_back(entry);
}
}
DxbcIsgn::~DxbcIsgn() {
}
const DxbcSgnEntry* DxbcIsgn::findByRegister(uint32_t registerId) const {
for (auto e = this->begin(); e != this->end(); e++) {
if (e->registerId == registerId)
return &(*e);
}
return nullptr;
}
const DxbcSgnEntry* DxbcIsgn::find(
const std::string& semanticName,
uint32_t semanticIndex,
uint32_t streamId) const {
for (auto e = this->begin(); e != this->end(); e++) {
if (e->semanticIndex == semanticIndex
&& e->streamId == streamId
&& compareSemanticNames(semanticName, e->semanticName))
return &(*e);
}
return nullptr;
}
DxbcRegMask DxbcIsgn::regMask(
uint32_t registerId) const {
DxbcRegMask mask;
for (auto e = this->begin(); e != this->end(); e++) {
if (e->registerId == registerId)
mask |= e->componentMask;
}
return mask;
}
uint32_t DxbcIsgn::maxRegisterCount() const {
uint32_t result = 0;
for (auto e = this->begin(); e != this->end(); e++)
result = std::max(result, e->registerId + 1);
return result;
}
void DxbcIsgn::printEntries() const {
for (auto entry = this->begin(); entry != this->end(); entry++) {
Logger::debug(str::format("SGN Entry:\n\t",
"semanticName: ", entry->semanticName, "\n\t",
"semanticIndex: ", entry->semanticIndex, "\n\t",
"registerId: ", entry->registerId, "\n\t",
"componentMask: ", entry->componentMask.maskString(), "\n\t",
"componentType: ", entry->componentType, "\n\t",
"systemValue: ", entry->systemValue, "\n\t",
"streamId: ", entry->streamId, "\n",
"\n"));
}
}
bool DxbcIsgn::compareSemanticNames(
const std::string& a, const std::string& b) {
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); i++) {
char ac = a[i];
char bc = b[i];
if (ac != bc) {
if (ac >= 'A' && ac <= 'Z') ac += 'a' - 'A';
if (bc >= 'A' && bc <= 'Z') bc += 'a' - 'A';
if (ac != bc)
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,69 @@
#pragma once
#include <cctype>
#include "dxbc_common.h"
#include "dxbc_decoder.h"
#include "dxbc_enums.h"
#include "dxbc_reader.h"
namespace dxvk {
/**
* \brief Signature entry
*
* Stores the semantic name of an input or
* output and the corresponding register.
*/
struct DxbcSgnEntry {
std::string semanticName;
uint32_t semanticIndex;
uint32_t registerId;
DxbcRegMask componentMask;
DxbcScalarType componentType;
DxbcSystemValue systemValue;
uint32_t streamId;
};
/**
* \brief Input/Output signature chunk
*
* Stores information about the input and
* output registers used by the shader stage.
*/
class DxbcIsgn : public RcObject {
public:
DxbcIsgn(DxbcReader reader, DxbcTag tag);
~DxbcIsgn();
auto begin() const { return m_entries.cbegin(); }
auto end () const { return m_entries.cend(); }
const DxbcSgnEntry* findByRegister(
uint32_t registerId) const;
const DxbcSgnEntry* find(
const std::string& semanticName,
uint32_t semanticIndex,
uint32_t streamIndex) const;
DxbcRegMask regMask(
uint32_t registerId) const;
uint32_t maxRegisterCount() const;
void printEntries() const;
static bool compareSemanticNames(
const std::string& a,
const std::string& b);
private:
std::vector<DxbcSgnEntry> m_entries;
};
}

View file

@ -0,0 +1,24 @@
#include "dxbc_chunk_shex.h"
namespace dxvk {
DxbcShex::DxbcShex(DxbcReader reader) {
// The shader version and type are stored in a 32-bit unit,
// where the first byte contains the major and minor version
// numbers, and the high word contains the program type.
reader.skip(2);
auto pType = reader.readEnum<DxbcProgramType>();
m_programInfo = DxbcProgramInfo(pType);
// Read the actual shader code as an array of DWORDs.
auto codeLength = reader.readu32() - 2;
m_code.resize(codeLength);
reader.read(m_code.data(), codeLength * sizeof(uint32_t));
}
DxbcShex::~DxbcShex() {
}
}

View file

@ -0,0 +1,39 @@
#pragma once
#include "dxbc_common.h"
#include "dxbc_decoder.h"
#include "dxbc_reader.h"
namespace dxvk {
/**
* \brief Shader code chunk
*
* Stores the DXBC shader code itself, as well
* as some meta info about the shader, i.e. what
* type of shader this is.
*/
class DxbcShex : public RcObject {
public:
DxbcShex(DxbcReader reader);
~DxbcShex();
DxbcProgramInfo programInfo() const {
return m_programInfo;
}
DxbcCodeSlice slice() const {
return DxbcCodeSlice(m_code.data(),
m_code.data() + m_code.size());
}
private:
DxbcProgramInfo m_programInfo;
std::vector<uint32_t> m_code;
};
}

32
src/dxbc/dxbc_common.cpp Normal file
View file

@ -0,0 +1,32 @@
#include "dxbc_common.h"
namespace dxvk {
VkShaderStageFlagBits DxbcProgramInfo::shaderStage() const {
switch (m_type) {
case DxbcProgramType::PixelShader : return VK_SHADER_STAGE_FRAGMENT_BIT;
case DxbcProgramType::VertexShader : return VK_SHADER_STAGE_VERTEX_BIT;
case DxbcProgramType::GeometryShader : return VK_SHADER_STAGE_GEOMETRY_BIT;
case DxbcProgramType::HullShader : return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
case DxbcProgramType::DomainShader : return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
case DxbcProgramType::ComputeShader : return VK_SHADER_STAGE_COMPUTE_BIT;
}
throw DxvkError("DxbcProgramInfo::shaderStage: Unsupported program type");
}
spv::ExecutionModel DxbcProgramInfo::executionModel() const {
switch (m_type) {
case DxbcProgramType::PixelShader : return spv::ExecutionModelFragment;
case DxbcProgramType::VertexShader : return spv::ExecutionModelVertex;
case DxbcProgramType::GeometryShader : return spv::ExecutionModelGeometry;
case DxbcProgramType::HullShader : return spv::ExecutionModelTessellationControl;
case DxbcProgramType::DomainShader : return spv::ExecutionModelTessellationEvaluation;
case DxbcProgramType::ComputeShader : return spv::ExecutionModelGLCompute;
}
throw DxvkError("DxbcProgramInfo::executionModel: Unsupported program type");
}
}

69
src/dxbc/dxbc_common.h Normal file
View file

@ -0,0 +1,69 @@
#pragma once
#include "dxbc_include.h"
#include <spirv/unified1/spirv.hpp>
namespace dxvk {
/**
* \brief DXBC Program type
*
* Defines the shader stage that a DXBC
* module has been compiled form.
*/
enum class DxbcProgramType : uint16_t {
PixelShader = 0,
VertexShader = 1,
GeometryShader = 2,
HullShader = 3,
DomainShader = 4,
ComputeShader = 5,
};
/**
* \brief DXBC shader info
*
* Stores the shader program type.
*/
class DxbcProgramInfo {
public:
DxbcProgramInfo() { }
DxbcProgramInfo(DxbcProgramType type)
: m_type(type) { }
/**
* \brief Program type
* \returns Program type
*/
DxbcProgramType type() const {
return m_type;
}
/**
* \brief Vulkan shader stage
*
* The \c VkShaderStageFlagBits constant
* that corresponds to the program type.
* \returns Vulkan shaer stage
*/
VkShaderStageFlagBits shaderStage() const;
/**
* \brief SPIR-V execution model
*
* The execution model that corresponds
* to the Vulkan shader stage.
* \returns SPIR-V execution model
*/
spv::ExecutionModel executionModel() const;
private:
DxbcProgramType m_type = DxbcProgramType::PixelShader;
};
}

7937
src/dxbc/dxbc_compiler.cpp Normal file

File diff suppressed because it is too large Load diff

1276
src/dxbc/dxbc_compiler.h Normal file

File diff suppressed because it is too large Load diff

360
src/dxbc/dxbc_decoder.cpp Normal file
View file

@ -0,0 +1,360 @@
#include "dxbc_decoder.h"
namespace dxvk {
const uint32_t* DxbcCodeSlice::ptrAt(uint32_t id) const {
if (m_ptr + id >= m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return m_ptr + id;
}
uint32_t DxbcCodeSlice::at(uint32_t id) const {
if (m_ptr + id >= m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return m_ptr[id];
}
uint32_t DxbcCodeSlice::read() {
if (m_ptr >= m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return *(m_ptr++);
}
DxbcCodeSlice DxbcCodeSlice::take(uint32_t n) const {
if (m_ptr + n > m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return DxbcCodeSlice(m_ptr, m_ptr + n);
}
DxbcCodeSlice DxbcCodeSlice::skip(uint32_t n) const {
if (m_ptr + n > m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return DxbcCodeSlice(m_ptr + n, m_end);
}
void DxbcDecodeContext::decodeInstruction(DxbcCodeSlice& code) {
const uint32_t token0 = code.at(0);
// Initialize the instruction structure. Some of these values
// may not get written otherwise while decoding the instruction.
m_instruction.op = static_cast<DxbcOpcode>(bit::extract(token0, 0, 10));
m_instruction.opClass = DxbcInstClass::Undefined;
m_instruction.sampleControls = { 0, 0, 0 };
m_instruction.dstCount = 0;
m_instruction.srcCount = 0;
m_instruction.immCount = 0;
m_instruction.dst = m_dstOperands.data();
m_instruction.src = m_srcOperands.data();
m_instruction.imm = m_immOperands.data();
m_instruction.customDataType = DxbcCustomDataClass::Comment;
m_instruction.customDataSize = 0;
m_instruction.customData = nullptr;
// Reset the index pointer, which may still contain
// a non-zero value from the previous iteration
m_indexId = 0;
// Instruction length, in DWORDs. This includes the token
// itself and any other prefix that an instruction may have.
uint32_t length = 0;
if (m_instruction.op == DxbcOpcode::CustomData) {
length = code.at(1);
this->decodeCustomData(code.take(length));
} else {
length = bit::extract(token0, 24, 30);
this->decodeOperation(code.take(length));
}
// Advance the caller's slice to the next token so that
// they can make consecutive calls to decodeInstruction()
code = code.skip(length);
}
void DxbcDecodeContext::decodeCustomData(DxbcCodeSlice code) {
const uint32_t blockLength = code.at(1);
if (blockLength < 2) {
Logger::err("DxbcDecodeContext: Invalid custom data block");
return;
}
// Custom data blocks have their own instruction class
m_instruction.op = DxbcOpcode::CustomData;
m_instruction.opClass = DxbcInstClass::CustomData;
// We'll point into the code buffer rather than making a copy
m_instruction.customDataType = static_cast<DxbcCustomDataClass>(
bit::extract(code.at(0), 11, 31));
m_instruction.customDataSize = blockLength - 2;
m_instruction.customData = code.ptrAt(2);
}
void DxbcDecodeContext::decodeOperation(DxbcCodeSlice code) {
uint32_t token = code.read();
// Result modifiers, which are applied to common ALU ops
m_instruction.modifiers.saturate = !!bit::extract(token, 13, 13);
m_instruction.modifiers.precise = !!bit::extract(token, 19, 22);
// Opcode controls. It will depend on the
// opcode itself which ones are valid.
m_instruction.controls = DxbcShaderOpcodeControls(token);
// Process extended opcode tokens
while (bit::extract(token, 31, 31)) {
token = code.read();
const DxbcExtOpcode extOpcode
= static_cast<DxbcExtOpcode>(bit::extract(token, 0, 5));
switch (extOpcode) {
case DxbcExtOpcode::SampleControls: {
struct {
int u : 4;
int v : 4;
int w : 4;
} aoffimmi;
aoffimmi.u = bit::extract(token, 9, 12);
aoffimmi.v = bit::extract(token, 13, 16);
aoffimmi.w = bit::extract(token, 17, 20);
// Four-bit signed numbers, sign-extend them
m_instruction.sampleControls.u = aoffimmi.u;
m_instruction.sampleControls.v = aoffimmi.v;
m_instruction.sampleControls.w = aoffimmi.w;
} break;
case DxbcExtOpcode::ResourceDim:
case DxbcExtOpcode::ResourceReturnType:
break; // part of resource description
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled extended opcode: ",
extOpcode));
}
}
// Retrieve the instruction format in order to parse the
// operands. Doing this mostly automatically means that
// the compiler can rely on the operands being valid.
const DxbcInstFormat format = dxbcInstructionFormat(m_instruction.op);
m_instruction.opClass = format.instructionClass;
for (uint32_t i = 0; i < format.operandCount; i++)
this->decodeOperand(code, format.operands[i]);
}
void DxbcDecodeContext::decodeComponentSelection(DxbcRegister& reg, uint32_t token) {
// Pick the correct component selection mode based on the
// component count. We'll simplify this here so that the
// compiler can assume that everything is a 4D vector.
reg.componentCount = static_cast<DxbcComponentCount>(bit::extract(token, 0, 1));
switch (reg.componentCount) {
// No components - used for samplers etc.
case DxbcComponentCount::Component0:
reg.mask = DxbcRegMask(false, false, false, false);
reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0);
break;
// One component - used for immediates
// and a few built-in registers.
case DxbcComponentCount::Component1:
reg.mask = DxbcRegMask(true, false, false, false);
reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0);
break;
// Four components - everything else. This requires us
// to actually parse the component selection mode.
case DxbcComponentCount::Component4: {
const DxbcRegMode componentMode =
static_cast<DxbcRegMode>(bit::extract(token, 2, 3));
switch (componentMode) {
// Write mask for destination operands
case DxbcRegMode::Mask:
reg.mask = bit::extract(token, 4, 7);
reg.swizzle = DxbcRegSwizzle(0, 1, 2, 3);
break;
// Swizzle for source operands (including resources)
case DxbcRegMode::Swizzle:
reg.mask = DxbcRegMask(true, true, true, true);
reg.swizzle = DxbcRegSwizzle(
bit::extract(token, 4, 5),
bit::extract(token, 6, 7),
bit::extract(token, 8, 9),
bit::extract(token, 10, 11));
break;
// Selection of one component. We can generate both a
// mask and a swizzle for this so that the compiler
// won't have to deal with this case specifically.
case DxbcRegMode::Select1: {
const uint32_t n = bit::extract(token, 4, 5);
reg.mask = DxbcRegMask(n == 0, n == 1, n == 2, n == 3);
reg.swizzle = DxbcRegSwizzle(n, n, n, n);
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component selection mode");
}
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component count");
}
}
void DxbcDecodeContext::decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) {
while (bit::extract(token, 31, 31)) {
token = code.read();
// Type of the extended operand token
const DxbcOperandExt extTokenType =
static_cast<DxbcOperandExt>(bit::extract(token, 0, 5));
switch (extTokenType) {
// Operand modifiers, which are used to manipulate the
// value of a source operand during the load operation
case DxbcOperandExt::OperandModifier:
reg.modifiers = bit::extract(token, 6, 13);
break;
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled extended operand token: ",
extTokenType));
}
}
}
void DxbcDecodeContext::decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg) {
if (reg.type == DxbcOperandType::Imm32
|| reg.type == DxbcOperandType::Imm64) {
switch (reg.componentCount) {
// This is commonly used if only one vector
// component is involved in an operation
case DxbcComponentCount::Component1: {
reg.imm.u32_1 = code.read();
} break;
// Typical four-component vector
case DxbcComponentCount::Component4: {
reg.imm.u32_4[0] = code.read();
reg.imm.u32_4[1] = code.read();
reg.imm.u32_4[2] = code.read();
reg.imm.u32_4[3] = code.read();
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component count for immediate operand");
}
}
}
void DxbcDecodeContext::decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) {
reg.idxDim = bit::extract(token, 20, 21);
for (uint32_t i = 0; i < reg.idxDim; i++) {
// An index can be encoded in various different ways
const DxbcOperandIndexRepresentation repr =
static_cast<DxbcOperandIndexRepresentation>(
bit::extract(token, 22 + 3 * i, 24 + 3 * i));
switch (repr) {
case DxbcOperandIndexRepresentation::Imm32:
reg.idx[i].offset = static_cast<int32_t>(code.read());
reg.idx[i].relReg = nullptr;
break;
case DxbcOperandIndexRepresentation::Relative:
reg.idx[i].offset = 0;
reg.idx[i].relReg = &m_indices.at(m_indexId);
this->decodeRegister(code,
m_indices.at(m_indexId++),
DxbcScalarType::Sint32);
break;
case DxbcOperandIndexRepresentation::Imm32Relative:
reg.idx[i].offset = static_cast<int32_t>(code.read());
reg.idx[i].relReg = &m_indices.at(m_indexId);
this->decodeRegister(code,
m_indices.at(m_indexId++),
DxbcScalarType::Sint32);
break;
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled index representation: ",
repr));
}
}
}
void DxbcDecodeContext::decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type) {
const uint32_t token = code.read();
reg.type = static_cast<DxbcOperandType>(bit::extract(token, 12, 19));
reg.dataType = type;
reg.modifiers = 0;
reg.idxDim = 0;
for (uint32_t i = 0; i < DxbcMaxRegIndexDim; i++) {
reg.idx[i].relReg = nullptr;
reg.idx[i].offset = 0;
}
this->decodeComponentSelection(reg, token);
this->decodeOperandExtensions(code, reg, token);
this->decodeOperandImmediates(code, reg);
this->decodeOperandIndex(code, reg, token);
}
void DxbcDecodeContext::decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type) {
imm.u32 = code.read();
}
void DxbcDecodeContext::decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format) {
switch (format.kind) {
case DxbcOperandKind::DstReg: {
const uint32_t operandId = m_instruction.dstCount++;
this->decodeRegister(code, m_dstOperands.at(operandId), format.type);
} break;
case DxbcOperandKind::SrcReg: {
const uint32_t operandId = m_instruction.srcCount++;
this->decodeRegister(code, m_srcOperands.at(operandId), format.type);
} break;
case DxbcOperandKind::Imm32: {
const uint32_t operandId = m_instruction.immCount++;
this->decodeImm32(code, m_immOperands.at(operandId), format.type);
} break;
default:
throw DxvkError("DxbcDecodeContext: Invalid operand format");
}
}
}

505
src/dxbc/dxbc_decoder.h Normal file
View file

@ -0,0 +1,505 @@
#pragma once
#include <array>
#include "dxbc_common.h"
#include "dxbc_decoder.h"
#include "dxbc_defs.h"
#include "dxbc_enums.h"
#include "dxbc_names.h"
namespace dxvk {
constexpr size_t DxbcMaxRegIndexDim = 3;
struct DxbcRegister;
/**
* \brief Source operand modifiers
*
* These are applied after loading
* an operand register.
*/
enum class DxbcRegModifier : uint32_t {
Neg = 0,
Abs = 1,
};
using DxbcRegModifiers = Flags<DxbcRegModifier>;
/**
* \brief Constant buffer binding
*
* Stores information required to
* access a constant buffer.
*/
struct DxbcConstantBuffer {
uint32_t varId = 0;
uint32_t size = 0;
};
/**
* \brief Sampler binding
*
* Stores a sampler variable that can be
* used together with a texture resource.
*/
struct DxbcSampler {
uint32_t varId = 0;
uint32_t typeId = 0;
};
/**
* \brief Image type information
*/
struct DxbcImageInfo {
spv::Dim dim = spv::Dim1D;
uint32_t array = 0;
uint32_t ms = 0;
uint32_t sampled = 0;
VkImageViewType vtype = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
};
/**
* \brief Shader resource binding
*
* Stores a resource variable
* and associated type IDs.
*/
struct DxbcShaderResource {
DxbcResourceType type = DxbcResourceType::Typed;
DxbcImageInfo imageInfo;
uint32_t varId = 0;
uint32_t specId = 0;
DxbcScalarType sampledType = DxbcScalarType::Float32;
uint32_t sampledTypeId = 0;
uint32_t imageTypeId = 0;
uint32_t colorTypeId = 0;
uint32_t depthTypeId = 0;
uint32_t structStride = 0;
bool isRawSsbo = false;
};
/**
* \brief Unordered access binding
*
* Stores a resource variable that is provided
* by a UAV, as well as associated type IDs.
*/
struct DxbcUav {
DxbcResourceType type = DxbcResourceType::Typed;
DxbcImageInfo imageInfo;
uint32_t varId = 0;
uint32_t ctrId = 0;
uint32_t specId = 0;
DxbcScalarType sampledType = DxbcScalarType::Float32;
uint32_t sampledTypeId = 0;
uint32_t imageTypeId = 0;
uint32_t structStride = 0;
uint32_t coherence = 0;
bool isRawSsbo = false;
};
/**
* \brief Component swizzle
*
* Maps vector components to
* other vector components.
*/
class DxbcRegSwizzle {
public:
DxbcRegSwizzle() { }
DxbcRegSwizzle(uint32_t x, uint32_t y, uint32_t z, uint32_t w)
: m_mask((x << 0) | (y << 2) | (z << 4) | (w << 6)) { }
uint32_t operator [] (uint32_t id) const {
return (m_mask >> (id + id)) & 0x3;
}
bool operator == (const DxbcRegSwizzle& other) const { return m_mask == other.m_mask; }
bool operator != (const DxbcRegSwizzle& other) const { return m_mask != other.m_mask; }
private:
uint8_t m_mask = 0;
};
/**
* \brief Component mask
*
* Enables access to certain
* subset of vector components.
*/
class DxbcRegMask {
public:
DxbcRegMask() { }
DxbcRegMask(uint32_t mask) : m_mask(mask) { }
DxbcRegMask(bool x, bool y, bool z, bool w)
: m_mask((x ? 0x1 : 0) | (y ? 0x2 : 0)
| (z ? 0x4 : 0) | (w ? 0x8 : 0)) { }
bool operator [] (uint32_t id) const {
return (m_mask >> id) & 1;
}
uint32_t popCount() const {
const uint8_t n[16] = { 0, 1, 1, 2, 1, 2, 2, 3,
1, 2, 2, 3, 2, 3, 3, 4 };
return n[m_mask & 0xF];
}
uint32_t firstSet() const {
const uint8_t n[16] = { 4, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0 };
return n[m_mask & 0xF];
}
uint32_t minComponents() const {
const uint8_t n[16] = { 0, 1, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4 };
return n[m_mask & 0xF];
}
bool operator == (const DxbcRegMask& other) const { return m_mask == other.m_mask; }
bool operator != (const DxbcRegMask& other) const { return m_mask != other.m_mask; }
DxbcRegMask& operator |= (const DxbcRegMask& other) {
m_mask |= other.m_mask;
return *this;
}
static DxbcRegMask firstN(uint32_t n) {
return DxbcRegMask(n >= 1, n >= 2, n >= 3, n >= 4);
}
static DxbcRegMask select(uint32_t n) {
return DxbcRegMask(n == 0, n == 1, n == 2, n == 3);
}
std::string maskString() const {
std::string out = "";
out += (m_mask & 0x1) ? "x" : "";
out += (m_mask & 0x2) ? "y" : "";
out += (m_mask & 0x4) ? "z" : "";
out += (m_mask & 0x8) ? "w" : "";
return out;
}
operator bool () const {
return m_mask != 0;
}
private:
uint8_t m_mask = 0;
};
/**
* \brief System value mapping
*
* Maps a system value to a given set of
* components of an input or output register.
*/
struct DxbcSvMapping {
uint32_t regId;
DxbcRegMask regMask;
DxbcSystemValue sv;
};
struct DxbcRegIndex {
DxbcRegister* relReg;
int32_t offset;
};
/**
* \brief Instruction operand
*/
struct DxbcRegister {
DxbcOperandType type;
DxbcScalarType dataType;
DxbcComponentCount componentCount;
uint32_t idxDim;
DxbcRegIndex idx[DxbcMaxRegIndexDim];
DxbcRegMask mask;
DxbcRegSwizzle swizzle;
DxbcRegModifiers modifiers;
union {
uint32_t u32_4[4];
uint32_t u32_1;
} imm;
};
/**
* \brief Instruction result modifiers
*
* Modifiers that are applied
* to all destination operands.
*/
struct DxbcOpModifiers {
bool saturate;
bool precise;
};
/**
* \brief Opcode controls
*
* Instruction-specific controls. Usually,
* only one of the members will be valid.
*/
class DxbcShaderOpcodeControls {
public:
DxbcShaderOpcodeControls()
: m_bits(0) { }
DxbcShaderOpcodeControls(uint32_t bits)
: m_bits(bits) { }
DxbcInstructionReturnType returnType() const {
return DxbcInstructionReturnType(bit::extract(m_bits, 11, 11));
}
DxbcGlobalFlags globalFlags() const {
return DxbcGlobalFlags(bit::extract(m_bits, 11, 14));
}
DxbcZeroTest zeroTest() const {
return DxbcZeroTest(bit::extract(m_bits, 18, 18));
}
DxbcSyncFlags syncFlags() const {
return DxbcSyncFlags(bit::extract(m_bits, 11, 14));
}
DxbcResourceDim resourceDim() const {
return DxbcResourceDim(bit::extract(m_bits, 11, 15));
}
DxbcResinfoType resinfoType() const {
return DxbcResinfoType(bit::extract(m_bits, 11, 12));
}
DxbcInterpolationMode interpolation() const {
return DxbcInterpolationMode(bit::extract(m_bits, 11, 14));
}
DxbcSamplerMode samplerMode() const {
return DxbcSamplerMode(bit::extract(m_bits, 11, 14));
}
DxbcPrimitiveTopology primitiveTopology() const {
return DxbcPrimitiveTopology(bit::extract(m_bits, 11, 17));
}
DxbcPrimitive primitive() const {
return DxbcPrimitive(bit::extract(m_bits, 11, 16));
}
DxbcTessDomain tessDomain() const {
return DxbcTessDomain(bit::extract(m_bits, 11, 12));
}
DxbcTessOutputPrimitive tessOutputPrimitive() const {
return DxbcTessOutputPrimitive(bit::extract(m_bits, 11, 13));
}
DxbcTessPartitioning tessPartitioning() const {
return DxbcTessPartitioning(bit::extract(m_bits, 11, 13));
}
DxbcUavFlags uavFlags() const {
return DxbcUavFlags(bit::extract(m_bits, 16, 17));
}
DxbcConstantBufferAccessType accessType() const {
return DxbcConstantBufferAccessType(bit::extract(m_bits, 11, 11));
}
uint32_t controlPointCount() const {
return bit::extract(m_bits, 11, 16);
}
bool precise() const {
return bit::extract(m_bits, 19, 22) != 0;
}
private:
uint32_t m_bits;
};
/**
* \brief Sample controls
*
* Constant texel offset with
* values raning from -8 to 7.
*/
struct DxbcShaderSampleControls {
int u, v, w;
};
/**
* \brief Immediate value
*
* Immediate argument represented either
* as a 32-bit or 64-bit unsigned integer,
* or a 32-bit or 32-bit floating point number.
*/
union DxbcImmediate {
float f32;
double f64;
uint32_t u32;
uint64_t u64;
};
/**
* \brief Shader instruction
*
* Note that this structure may store pointer to
* external structures, such as the original code
* buffer. This is safe to use if and only if:
* - The \ref DxbcDecodeContext that created it
* still exists and was not moved
* - The code buffer that was being decoded
* still exists and was not moved.
*/
struct DxbcShaderInstruction {
DxbcOpcode op;
DxbcInstClass opClass;
DxbcOpModifiers modifiers;
DxbcShaderOpcodeControls controls;
DxbcShaderSampleControls sampleControls;
uint32_t dstCount;
uint32_t srcCount;
uint32_t immCount;
const DxbcRegister* dst;
const DxbcRegister* src;
const DxbcImmediate* imm;
DxbcCustomDataClass customDataType;
uint32_t customDataSize;
const uint32_t* customData;
};
/**
* \brief DXBC code slice
*
* Convenient pointer pair that allows
* reading the code word stream safely.
*/
class DxbcCodeSlice {
public:
DxbcCodeSlice(
const uint32_t* ptr,
const uint32_t* end)
: m_ptr(ptr), m_end(end) { }
const uint32_t* ptrAt(uint32_t id) const;
uint32_t at(uint32_t id) const;
uint32_t read();
DxbcCodeSlice take(uint32_t n) const;
DxbcCodeSlice skip(uint32_t n) const;
bool atEnd() const {
return m_ptr == m_end;
}
private:
const uint32_t* m_ptr = nullptr;
const uint32_t* m_end = nullptr;
};
/**
* \brief Decode context
*
* Stores data that is required to decode a single
* instruction. This data is not persistent, so it
* should be forwarded to the compiler right away.
*/
class DxbcDecodeContext {
public:
/**
* \brief Retrieves current instruction
*
* This is only valid after a call to \ref decode.
* \returns Reference to last decoded instruction
*/
const DxbcShaderInstruction& getInstruction() const {
return m_instruction;
}
/**
* \brief Decodes an instruction
*
* This also advances the given code slice by the
* number of dwords consumed by the instruction.
* \param [in] code Code slice
*/
void decodeInstruction(DxbcCodeSlice& code);
private:
DxbcShaderInstruction m_instruction;
std::array<DxbcRegister, 8> m_dstOperands;
std::array<DxbcRegister, 8> m_srcOperands;
std::array<DxbcImmediate, 4> m_immOperands;
std::array<DxbcRegister, 12> m_indices;
// Index into the indices array. Used when decoding
// instruction operands with relative indexing.
uint32_t m_indexId = 0;
void decodeCustomData(DxbcCodeSlice code);
void decodeOperation(DxbcCodeSlice code);
void decodeComponentSelection(DxbcRegister& reg, uint32_t token);
void decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token);
void decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg);
void decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token);
void decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type);
void decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type);
void decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format);
};
}

1255
src/dxbc/dxbc_defs.cpp Normal file

File diff suppressed because it is too large Load diff

104
src/dxbc/dxbc_defs.h Normal file
View file

@ -0,0 +1,104 @@
#pragma once
#include "dxbc_enums.h"
namespace dxvk {
constexpr size_t DxbcMaxInterfaceRegs = 32;
constexpr size_t DxbcMaxOperandCount = 8;
/**
* \brief Operand kind
*
* In the instruction format definition, this specified
* whether an operand uses an actual operand token, or
* whether it is stored as an immediate value.
*/
enum class DxbcOperandKind {
DstReg, ///< Destination register
SrcReg, ///< Source register
Imm32, ///< Constant number
};
/**
* \brief Instruction class
*
* Instructions with a similar format are grouped into
* instruction classes in order to make implementing
* new instructions easier.
*/
enum class DxbcInstClass {
Declaration, ///< Interface or resource declaration
CustomData, ///< Immediate constant buffer
ControlFlow, ///< Control flow instructions
GeometryEmit, ///< Special geometry shader instructions
Atomic, ///< Atomic operations
AtomicCounter, ///< Atomic counter operations
Barrier, ///< Execution or memory barrier
BitExtract, ///< Bit field extract operations
BitInsert, ///< Bit field insert operations
BitScan, ///< Bit scan operations
BufferQuery, ///< Buffer query instruction
BufferLoad, ///< Structured or raw buffer load
BufferStore, ///< Structured or raw buffer store
ConvertFloat16, ///< 16-bit float packing/unpacking
ConvertFloat64, ///< 64-bit float conversion
HullShaderPhase, ///< Hull shader phase declaration
HullShaderInstCnt, ///< Hull shader phase instance count
Interpolate, ///< Input attribute interpolation
NoOperation, ///< The most useful instruction class
SparseCheckAccess, ///< Verifies sparse resource access
TextureQuery, ///< Texture query instruction
TextureQueryLod, ///< Texture LOD query instruction
TextureQueryMs, ///< Multisample texture query
TextureQueryMsPos, ///< Sample position query
TextureFetch, ///< Texture fetch instruction
TextureGather, ///< Texture gather instruction
TextureSample, ///< Texture sampling instruction
TypedUavLoad, ///< Typed UAV load
TypedUavStore, ///< Typed UAV store
VectorAlu, ///< Component-wise vector instructions
VectorCmov, ///< Component-wise conditional move
VectorCmp, ///< Component-wise vector comparison
VectorDeriv, ///< Vector derivatives
VectorDot, ///< Dot product instruction
VectorIdiv, ///< Component-wise integer division
VectorImul, ///< Component-wise integer multiplication
VectorMsad, ///< Component-wise sum of absolute difference
VectorShift, ///< Bit shift operations on vectors
VectorSinCos, ///< Sine and Cosine instruction
Undefined, ///< Instruction code not defined
};
/**
* \brief Instruction operand format
*
* Stores the kind and the expected data type
* of an operand. Used when parsing instructions.
*/
struct DxbcInstOperandFormat {
DxbcOperandKind kind;
DxbcScalarType type;
};
/**
* \brief Instruction format
*
* Defines the instruction class as well as
* the format of the insttruction operands.
*/
struct DxbcInstFormat {
uint32_t operandCount = 0;
DxbcInstClass instructionClass = DxbcInstClass::Undefined;
DxbcInstOperandFormat operands[DxbcMaxOperandCount];
};
/**
* \brief Retrieves instruction format info
*
* \param [in] opcode The opcode to retrieve
* \returns Instruction format info
*/
DxbcInstFormat dxbcInstructionFormat(DxbcOpcode opcode);
}

655
src/dxbc/dxbc_enums.h Normal file
View file

@ -0,0 +1,655 @@
#pragma once
#include "dxbc_include.h"
#include "util_flags.h"
namespace dxvk {
/**
* \brief Instruction code listing
*/
enum class DxbcOpcode : uint32_t {
Add = 0,
And = 1,
Break = 2,
Breakc = 3,
Call = 4,
Callc = 5,
Case = 6,
Continue = 7,
Continuec = 8,
Cut = 9,
Default = 10,
DerivRtx = 11,
DerivRty = 12,
Discard = 13,
Div = 14,
Dp2 = 15,
Dp3 = 16,
Dp4 = 17,
Else = 18,
Emit = 19,
EmitThenCut = 20,
EndIf = 21,
EndLoop = 22,
EndSwitch = 23,
Eq = 24,
Exp = 25,
Frc = 26,
FtoI = 27,
FtoU = 28,
Ge = 29,
IAdd = 30,
If = 31,
IEq = 32,
IGe = 33,
ILt = 34,
IMad = 35,
IMax = 36,
IMin = 37,
IMul = 38,
INe = 39,
INeg = 40,
IShl = 41,
IShr = 42,
ItoF = 43,
Label = 44,
Ld = 45,
LdMs = 46,
Log = 47,
Loop = 48,
Lt = 49,
Mad = 50,
Min = 51,
Max = 52,
CustomData = 53,
Mov = 54,
Movc = 55,
Mul = 56,
Ne = 57,
Nop = 58,
Not = 59,
Or = 60,
ResInfo = 61,
Ret = 62,
Retc = 63,
RoundNe = 64,
RoundNi = 65,
RoundPi = 66,
RoundZ = 67,
Rsq = 68,
Sample = 69,
SampleC = 70,
SampleClz = 71,
SampleL = 72,
SampleD = 73,
SampleB = 74,
Sqrt = 75,
Switch = 76,
SinCos = 77,
UDiv = 78,
ULt = 79,
UGe = 80,
UMul = 81,
UMad = 82,
UMax = 83,
UMin = 84,
UShr = 85,
UtoF = 86,
Xor = 87,
DclResource = 88,
DclConstantBuffer = 89,
DclSampler = 90,
DclIndexRange = 91,
DclGsOutputPrimitiveTopology = 92,
DclGsInputPrimitive = 93,
DclMaxOutputVertexCount = 94,
DclInput = 95,
DclInputSgv = 96,
DclInputSiv = 97,
DclInputPs = 98,
DclInputPsSgv = 99,
DclInputPsSiv = 100,
DclOutput = 101,
DclOutputSgv = 102,
DclOutputSiv = 103,
DclTemps = 104,
DclIndexableTemp = 105,
DclGlobalFlags = 106,
Reserved0 = 107,
Lod = 108,
Gather4 = 109,
SamplePos = 110,
SampleInfo = 111,
Reserved1 = 112,
HsDecls = 113,
HsControlPointPhase = 114,
HsForkPhase = 115,
HsJoinPhase = 116,
EmitStream = 117,
CutStream = 118,
EmitThenCutStream = 119,
InterfaceCall = 120,
BufInfo = 121,
DerivRtxCoarse = 122,
DerivRtxFine = 123,
DerivRtyCoarse = 124,
DerivRtyFine = 125,
Gather4C = 126,
Gather4Po = 127,
Gather4PoC = 128,
Rcp = 129,
F32toF16 = 130,
F16toF32 = 131,
UAddc = 132,
USubb = 133,
CountBits = 134,
FirstBitHi = 135,
FirstBitLo = 136,
FirstBitShi = 137,
UBfe = 138,
IBfe = 139,
Bfi = 140,
BfRev = 141,
Swapc = 142,
DclStream = 143,
DclFunctionBody = 144,
DclFunctionTable = 145,
DclInterface = 146,
DclInputControlPointCount = 147,
DclOutputControlPointCount = 148,
DclTessDomain = 149,
DclTessPartitioning = 150,
DclTessOutputPrimitive = 151,
DclHsMaxTessFactor = 152,
DclHsForkPhaseInstanceCount = 153,
DclHsJoinPhaseInstanceCount = 154,
DclThreadGroup = 155,
DclUavTyped = 156,
DclUavRaw = 157,
DclUavStructured = 158,
DclThreadGroupSharedMemoryRaw = 159,
DclThreadGroupSharedMemoryStructured = 160,
DclResourceRaw = 161,
DclResourceStructured = 162,
LdUavTyped = 163,
StoreUavTyped = 164,
LdRaw = 165,
StoreRaw = 166,
LdStructured = 167,
StoreStructured = 168,
AtomicAnd = 169,
AtomicOr = 170,
AtomicXor = 171,
AtomicCmpStore = 172,
AtomicIAdd = 173,
AtomicIMax = 174,
AtomicIMin = 175,
AtomicUMax = 176,
AtomicUMin = 177,
ImmAtomicAlloc = 178,
ImmAtomicConsume = 179,
ImmAtomicIAdd = 180,
ImmAtomicAnd = 181,
ImmAtomicOr = 182,
ImmAtomicXor = 183,
ImmAtomicExch = 184,
ImmAtomicCmpExch = 185,
ImmAtomicIMax = 186,
ImmAtomicIMin = 187,
ImmAtomicUMax = 188,
ImmAtomicUMin = 189,
Sync = 190,
DAdd = 191,
DMax = 192,
DMin = 193,
DMul = 194,
DEq = 195,
DGe = 196,
DLt = 197,
DNe = 198,
DMov = 199,
DMovc = 200,
DtoF = 201,
FtoD = 202,
EvalSnapped = 203,
EvalSampleIndex = 204,
EvalCentroid = 205,
DclGsInstanceCount = 206,
Abort = 207,
DebugBreak = 208,
ReservedBegin11_1 = 209,
DDiv = 210,
DFma = 211,
DRcp = 212,
Msad = 213,
DtoI = 214,
DtoU = 215,
ItoD = 216,
UtoD = 217,
ReservedBegin11_2 = 218,
Gather4S = 219,
Gather4CS = 220,
Gather4PoS = 221,
Gather4PoCS = 222,
LdS = 223,
LdMsS = 224,
LdUavTypedS = 225,
LdRawS = 226,
LdStructuredS = 227,
SampleLS = 228,
SampleClzS = 229,
SampleClampS = 230,
SampleBClampS = 231,
SampleDClampS = 232,
SampleCClampS = 233,
CheckAccessFullyMapped = 234,
};
/**
* \brief Extended opcode
*/
enum class DxbcExtOpcode : uint32_t {
Empty = 0,
SampleControls = 1,
ResourceDim = 2,
ResourceReturnType = 3,
};
/**
* \brief Operand type
*
* Selects the 'register file' from which
* to retrieve an operand's value.
*/
enum class DxbcOperandType : uint32_t {
Temp = 0,
Input = 1,
Output = 2,
IndexableTemp = 3,
Imm32 = 4,
Imm64 = 5,
Sampler = 6,
Resource = 7,
ConstantBuffer = 8,
ImmediateConstantBuffer = 9,
Label = 10,
InputPrimitiveId = 11,
OutputDepth = 12,
Null = 13,
Rasterizer = 14,
OutputCoverageMask = 15,
Stream = 16,
FunctionBody = 17,
FunctionTable = 18,
Interface = 19,
FunctionInput = 20,
FunctionOutput = 21,
OutputControlPointId = 22,
InputForkInstanceId = 23,
InputJoinInstanceId = 24,
InputControlPoint = 25,
OutputControlPoint = 26,
InputPatchConstant = 27,
InputDomainPoint = 28,
ThisPointer = 29,
UnorderedAccessView = 30,
ThreadGroupSharedMemory = 31,
InputThreadId = 32,
InputThreadGroupId = 33,
InputThreadIdInGroup = 34,
InputCoverageMask = 35,
InputThreadIndexInGroup = 36,
InputGsInstanceId = 37,
OutputDepthGe = 38,
OutputDepthLe = 39,
CycleCounter = 40,
OutputStencilRef = 41,
InputInnerCoverage = 42,
};
/**
* \brief Number of components
*
* Used by operands to determine whether the
* operand has one, four or zero components.
*/
enum class DxbcComponentCount : uint32_t {
Component0 = 0,
Component1 = 1,
Component4 = 2,
};
/**
* \brief Component selection mode
*
* When an operand has four components, the
* component selection mode deterines which
* components are used for the operation.
*/
enum class DxbcRegMode : uint32_t {
Mask = 0,
Swizzle = 1,
Select1 = 2,
};
/**
* \brief Index representation
*
* Determines how an operand
* register index is stored.
*/
enum class DxbcOperandIndexRepresentation : uint32_t {
Imm32 = 0,
Imm64 = 1,
Relative = 2,
Imm32Relative = 3,
Imm64Relative = 4,
};
/**
* \brief Extended operand type
*/
enum class DxbcOperandExt : uint32_t {
OperandModifier = 1,
};
/**
* \brief Resource dimension
* The type of a resource.
*/
enum class DxbcResourceDim : uint32_t {
Unknown = 0,
Buffer = 1,
Texture1D = 2,
Texture2D = 3,
Texture2DMs = 4,
Texture3D = 5,
TextureCube = 6,
Texture1DArr = 7,
Texture2DArr = 8,
Texture2DMsArr = 9,
TextureCubeArr = 10,
RawBuffer = 11,
StructuredBuffer = 12,
};
/**
* \brief Resource return type
* Data type for resource read ops.
*/
enum class DxbcResourceReturnType : uint32_t {
Unorm = 1,
Snorm = 2,
Sint = 3,
Uint = 4,
Float = 5,
Mixed = 6, /// ?
Double = 7,
Continued = 8, /// ?
Unused = 9, /// ?
};
/**
* \brief Register component type
* Data type of a register component.
*/
enum class DxbcRegisterComponentType : uint32_t {
Unknown = 0,
Uint32 = 1,
Sint32 = 2,
Float32 = 3,
};
/**
* \brief Instruction return type
*/
enum class DxbcInstructionReturnType : uint32_t {
Float = 0,
Uint = 1,
};
enum class DxbcSystemValue : uint32_t {
None = 0,
Position = 1,
ClipDistance = 2,
CullDistance = 3,
RenderTargetId = 4,
ViewportId = 5,
VertexId = 6,
PrimitiveId = 7,
InstanceId = 8,
IsFrontFace = 9,
SampleIndex = 10,
FinalQuadUeq0EdgeTessFactor = 11,
FinalQuadVeq0EdgeTessFactor = 12,
FinalQuadUeq1EdgeTessFactor = 13,
FinalQuadVeq1EdgeTessFactor = 14,
FinalQuadUInsideTessFactor = 15,
FinalQuadVInsideTessFactor = 16,
FinalTriUeq0EdgeTessFactor = 17,
FinalTriVeq0EdgeTessFactor = 18,
FinalTriWeq0EdgeTessFactor = 19,
FinalTriInsideTessFactor = 20,
FinalLineDetailTessFactor = 21,
FinalLineDensityTessFactor = 22,
Target = 64,
Depth = 65,
Coverage = 66,
DepthGe = 67,
DepthLe = 68
};
enum class DxbcInterpolationMode : uint32_t {
Undefined = 0,
Constant = 1,
Linear = 2,
LinearCentroid = 3,
LinearNoPerspective = 4,
LinearNoPerspectiveCentroid = 5,
LinearSample = 6,
LinearNoPerspectiveSample = 7,
};
enum class DxbcGlobalFlag : uint32_t {
RefactoringAllowed = 0,
DoublePrecision = 1,
EarlyFragmentTests = 2,
RawStructuredBuffers = 3,
};
using DxbcGlobalFlags = Flags<DxbcGlobalFlag>;
enum class DxbcZeroTest : uint32_t {
TestZ = 0,
TestNz = 1,
};
enum class DxbcResinfoType : uint32_t {
Float = 0,
RcpFloat = 1,
Uint = 2,
};
enum class DxbcSyncFlag : uint32_t {
ThreadsInGroup = 0,
ThreadGroupSharedMemory = 1,
UavMemoryGroup = 2,
UavMemoryGlobal = 3,
};
using DxbcSyncFlags = Flags<DxbcSyncFlag>;
/**
* \brief Geometry shader input primitive
*/
enum class DxbcPrimitive : uint32_t {
Undefined = 0,
Point = 1,
Line = 2,
Triangle = 3,
LineAdj = 6,
TriangleAdj = 7,
Patch1 = 8,
Patch2 = 9,
Patch3 = 10,
Patch4 = 11,
Patch5 = 12,
Patch6 = 13,
Patch7 = 14,
Patch8 = 15,
Patch9 = 16,
Patch10 = 17,
Patch11 = 18,
Patch12 = 19,
Patch13 = 20,
Patch14 = 21,
Patch15 = 22,
Patch16 = 23,
Patch17 = 24,
Patch18 = 25,
Patch19 = 26,
Patch20 = 27,
Patch21 = 28,
Patch22 = 29,
Patch23 = 30,
Patch24 = 31,
Patch25 = 32,
Patch26 = 33,
Patch27 = 34,
Patch28 = 35,
Patch29 = 36,
Patch30 = 37,
Patch31 = 38,
Patch32 = 39,
};
/**
* \brief Geometry shader output topology
*/
enum class DxbcPrimitiveTopology : uint32_t {
Undefined = 0,
PointList = 1,
LineList = 2,
LineStrip = 3,
TriangleList = 4,
TriangleStrip = 5,
LineListAdj = 10,
LineStripAdj = 11,
TriangleListAdj = 12,
TriangleStripAdj = 13,
};
/**
* \brief Sampler operation mode
*/
enum class DxbcSamplerMode : uint32_t {
Default = 0,
Comparison = 1,
Mono = 2,
};
/**
* \brief Scalar value type
*
* Enumerates possible register component
* types. Scalar types are represented as
* a one-component vector type.
*/
enum class DxbcScalarType : uint32_t {
Uint32 = 0,
Uint64 = 1,
Sint32 = 2,
Sint64 = 3,
Float32 = 4,
Float64 = 5,
Bool = 6,
};
/**
* \brief Tessellator domain
*/
enum class DxbcTessDomain : uint32_t {
Undefined = 0,
Isolines = 1,
Triangles = 2,
Quads = 3,
};
/**
* \brief Tessellator partitioning
*/
enum class DxbcTessPartitioning : uint32_t {
Undefined = 0,
Integer = 1,
Pow2 = 2,
FractOdd = 3,
FractEven = 4,
};
/**
* \brief UAV definition flags
*/
enum class DxbcUavFlag : uint32_t {
GloballyCoherent = 0,
RasterizerOrdered = 1,
};
using DxbcUavFlags = Flags<DxbcUavFlag>;
/**
* \brief Tessellator output primitive
*/
enum class DxbcTessOutputPrimitive : uint32_t {
Undefined = 0,
Point = 1,
Line = 2,
TriangleCw = 3,
TriangleCcw = 4,
};
/**
* \brief Custom data class
*
* Stores which type of custom data is
* referenced by the instruction.
*/
enum class DxbcCustomDataClass : uint32_t {
Comment = 0,
DebugInfo = 1,
Opaque = 2,
ImmConstBuf = 3,
};
enum class DxbcResourceType : uint32_t {
Typed = 0,
Raw = 1,
Structured = 2,
};
enum class DxbcConstantBufferAccessType : uint32_t {
StaticallyIndexed = 0,
DynamicallyIndexed = 1,
};
}

30
src/dxbc/dxbc_header.cpp Normal file
View file

@ -0,0 +1,30 @@
#include "dxbc_header.h"
namespace dxvk {
DxbcHeader::DxbcHeader(DxbcReader& reader) {
// FourCC at the start of the file, must be 'DXBC'
DxbcTag fourcc = reader.readTag();
if (fourcc != "DXBC")
throw DxvkError("DxbcHeader::DxbcHeader: Invalid fourcc, expected 'DXBC'");
// Stuff we don't actually need to store
reader.skip(4 * sizeof(uint32_t)); // Check sum
reader.skip(1 * sizeof(uint32_t)); // Constant 1
reader.skip(1 * sizeof(uint32_t)); // Bytecode length
// Number of chunks in the file
uint32_t chunkCount = reader.readu32();
// Chunk offsets are stored immediately after
for (uint32_t i = 0; i < chunkCount; i++)
m_chunkOffsets.push_back(reader.readu32());
}
DxbcHeader::~DxbcHeader() {
}
}

48
src/dxbc/dxbc_header.h Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include <vector>
#include "dxbc_reader.h"
namespace dxvk {
/**
* \brief DXBC header
*
* Stores information about the shader file itself
* and the data chunks stored inside the file.
*/
class DxbcHeader {
public:
DxbcHeader(DxbcReader& reader);
~DxbcHeader();
/**
* \brief Number of chunks
* \returns Chunk count
*/
uint32_t numChunks() const {
return m_chunkOffsets.size();
}
/**
* \brief Chunk offset
*
* Retrieves the offset of a chunk, in
* bytes, from the start of the file.
* \param [in] chunkId Chunk index
* \returns Byte offset of that chunk
*/
uint32_t chunkOffset(uint32_t chunkId) const {
return m_chunkOffsets.at(chunkId);
}
private:
std::vector<uint32_t> m_chunkOffsets;
};
}

18
src/dxbc/dxbc_include.h Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <vulkan/vulkan.h>
#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/rc/util_rc.h"
#include "util/rc/util_rc_ptr.h"
#include "util/util_bit.h"
#include "util/util_enum.h"
#include "util/util_error.h"
#include "util/util_string.h"

59
src/dxbc/dxbc_modinfo.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include "dxbc_options.h"
namespace dxvk {
/**
* \brief Tessellation info
*
* Stores the maximum tessellation factor
* to export from tessellation shaders.
*/
struct DxbcTessInfo {
float maxTessFactor;
};
/**
* \brief Xfb capture entry
*
* Stores an output variable to capture,
* as well as the buffer to write it to.
*/
struct DxbcXfbEntry {
const char* semanticName;
uint32_t semanticIndex;
uint32_t componentIndex;
uint32_t componentCount;
uint32_t streamId;
uint32_t bufferId;
uint32_t offset;
};
/**
* \brief Xfb info
*
* Stores capture entries and output buffer
* strides. This structure must only be
* defined if \c entryCount is non-zero.
*/
struct DxbcXfbInfo {
uint32_t entryCount;
DxbcXfbEntry entries[128];
uint32_t strides[4];
int32_t rasterizedStream;
};
/**
* \brief Shader module info
*
* Stores information which may affect shader compilation.
* This data can be supplied by the client API implementation.
*/
struct DxbcModuleInfo {
DxbcOptions options;
DxbcTessInfo* tess = nullptr;
DxbcXfbInfo* xfb = nullptr;
};
}

116
src/dxbc/dxbc_module.cpp Normal file
View file

@ -0,0 +1,116 @@
#include "dxbc_analysis.h"
#include "dxbc_compiler.h"
#include "dxbc_module.h"
namespace dxvk {
DxbcModule::DxbcModule(DxbcReader& reader)
: m_header(reader) {
for (uint32_t i = 0; i < m_header.numChunks(); i++) {
// The chunk tag is stored at the beginning of each chunk
auto chunkReader = reader.clone(m_header.chunkOffset(i));
auto tag = chunkReader.readTag();
// The chunk size follows right after the four-character
// code. This does not include the eight bytes that are
// consumed by the FourCC and chunk length entry.
auto chunkLength = chunkReader.readu32();
chunkReader = chunkReader.clone(8);
chunkReader = chunkReader.resize(chunkLength);
if ((tag == "SHDR") || (tag == "SHEX"))
m_shexChunk = new DxbcShex(chunkReader);
if ((tag == "ISGN") || (tag == "ISG1"))
m_isgnChunk = new DxbcIsgn(chunkReader, tag);
if ((tag == "OSGN") || (tag == "OSG5") || (tag == "OSG1"))
m_osgnChunk = new DxbcIsgn(chunkReader, tag);
if ((tag == "PCSG") || (tag == "PSG1"))
m_psgnChunk = new DxbcIsgn(chunkReader, tag);
}
}
DxbcModule::~DxbcModule() {
}
DxbcCompiler::ShaderCreateInfo DxbcModule::compile(
const DxbcModuleInfo& moduleInfo,
const std::string& fileName) const {
if (m_shexChunk == nullptr)
throw DxvkError("DxbcModule::compile: No SHDR/SHEX chunk");
DxbcAnalysisInfo analysisInfo;
DxbcAnalyzer analyzer(moduleInfo,
m_shexChunk->programInfo(),
m_isgnChunk, m_osgnChunk,
m_psgnChunk, analysisInfo);
this->runAnalyzer(analyzer, m_shexChunk->slice());
DxbcCompiler compiler(
fileName, moduleInfo,
m_shexChunk->programInfo(),
m_isgnChunk, m_osgnChunk,
m_psgnChunk, analysisInfo);
this->runCompiler(compiler, m_shexChunk->slice());
return compiler.finalize();
}
DxbcCompiler::ShaderCreateInfo DxbcModule::compilePassthroughShader(
const DxbcModuleInfo& moduleInfo,
const std::string& fileName) const {
if (m_shexChunk == nullptr)
throw DxvkError("DxbcModule::compile: No SHDR/SHEX chunk");
DxbcAnalysisInfo analysisInfo;
DxbcCompiler compiler(
fileName, moduleInfo,
DxbcProgramType::GeometryShader,
m_osgnChunk, m_osgnChunk,
m_psgnChunk, analysisInfo);
compiler.processXfbPassthrough();
return compiler.finalize();
}
void DxbcModule::runAnalyzer(
DxbcAnalyzer& analyzer,
DxbcCodeSlice slice) const {
DxbcDecodeContext decoder;
while (!slice.atEnd()) {
decoder.decodeInstruction(slice);
analyzer.processInstruction(
decoder.getInstruction());
}
}
void DxbcModule::runCompiler(
DxbcCompiler& compiler,
DxbcCodeSlice slice) const {
DxbcDecodeContext decoder;
while (!slice.atEnd()) {
decoder.decodeInstruction(slice);
compiler.processInstruction(
decoder.getInstruction());
}
}
}

102
src/dxbc/dxbc_module.h Normal file
View file

@ -0,0 +1,102 @@
#pragma once
//#include "../dxvk/dxvk_shader.h"
#include <optional>
#include "dxbc_chunk_isgn.h"
#include "dxbc_chunk_shex.h"
#include "dxbc_header.h"
#include "dxbc_modinfo.h"
#include "dxbc_reader.h"
#include "dxbc_compiler.h"
// References used for figuring out DXBC:
// - https://github.com/tgjones/slimshader-cpp
// - Wine
namespace dxvk {
class DxbcAnalyzer;
class DxbcCompiler;
/**
* \brief DXBC shader module
*
* Reads the DXBC byte code and extracts information
* about the resource bindings and the instruction
* stream. A module can then be compiled to SPIR-V.
*/
class DxbcModule {
public:
DxbcModule(DxbcReader& reader);
~DxbcModule();
/**
* \brief Shader type
* \returns Shader type
*/
std::optional<DxbcProgramInfo> programInfo() const {
if (m_shexChunk == nullptr)
return std::nullopt;
return m_shexChunk->programInfo();
}
/**
* \brief Input and output signature chunks
*
* Parts of the D3D11 API need access to the
* input or output signature of the shader.
*/
Rc<DxbcIsgn> isgn() const { return m_isgnChunk; }
Rc<DxbcIsgn> osgn() const { return m_osgnChunk; }
/**
* \brief Compiles DXBC shader to SPIR-V module
*
* \param [in] moduleInfo DXBC module info
* \param [in] fileName File name, will be added to
* the compiled SPIR-V for debugging purposes.
* \returns The compiled shader object
*/
DxbcCompiler::ShaderCreateInfo compile(
const DxbcModuleInfo& moduleInfo,
const std::string& fileName) const;
/**
* \brief Compiles a pass-through geometry shader
*
* Applications can pass a vertex shader to create
* a geometry shader with stream output. In this
* case, we have to create a passthrough geometry
* shader, which operates in point to point mode.
* \param [in] moduleInfo DXBC module info
* \param [in] fileName SPIR-V shader name
*/
DxbcCompiler::ShaderCreateInfo compilePassthroughShader(
const DxbcModuleInfo& moduleInfo,
const std::string& fileName) const;
private:
DxbcHeader m_header;
Rc<DxbcIsgn> m_isgnChunk;
Rc<DxbcIsgn> m_osgnChunk;
Rc<DxbcIsgn> m_psgnChunk;
Rc<DxbcShex> m_shexChunk;
void runAnalyzer(
DxbcAnalyzer& analyzer,
DxbcCodeSlice slice) const;
void runCompiler(
DxbcCompiler& compiler,
DxbcCodeSlice slice) const;
};
}

445
src/dxbc/dxbc_names.cpp Normal file
View file

@ -0,0 +1,445 @@
#include "dxbc_names.h"
namespace dxvk {
std::ostream& operator << (std::ostream& os, DxbcOpcode e) {
switch (e) {
ENUM_NAME(DxbcOpcode::Add);
ENUM_NAME(DxbcOpcode::And);
ENUM_NAME(DxbcOpcode::Break);
ENUM_NAME(DxbcOpcode::Breakc);
ENUM_NAME(DxbcOpcode::Call);
ENUM_NAME(DxbcOpcode::Callc);
ENUM_NAME(DxbcOpcode::Case);
ENUM_NAME(DxbcOpcode::Continue);
ENUM_NAME(DxbcOpcode::Continuec);
ENUM_NAME(DxbcOpcode::Cut);
ENUM_NAME(DxbcOpcode::Default);
ENUM_NAME(DxbcOpcode::DerivRtx);
ENUM_NAME(DxbcOpcode::DerivRty);
ENUM_NAME(DxbcOpcode::Discard);
ENUM_NAME(DxbcOpcode::Div);
ENUM_NAME(DxbcOpcode::Dp2);
ENUM_NAME(DxbcOpcode::Dp3);
ENUM_NAME(DxbcOpcode::Dp4);
ENUM_NAME(DxbcOpcode::Else);
ENUM_NAME(DxbcOpcode::Emit);
ENUM_NAME(DxbcOpcode::EmitThenCut);
ENUM_NAME(DxbcOpcode::EndIf);
ENUM_NAME(DxbcOpcode::EndLoop);
ENUM_NAME(DxbcOpcode::EndSwitch);
ENUM_NAME(DxbcOpcode::Eq);
ENUM_NAME(DxbcOpcode::Exp);
ENUM_NAME(DxbcOpcode::Frc);
ENUM_NAME(DxbcOpcode::FtoI);
ENUM_NAME(DxbcOpcode::FtoU);
ENUM_NAME(DxbcOpcode::Ge);
ENUM_NAME(DxbcOpcode::IAdd);
ENUM_NAME(DxbcOpcode::If);
ENUM_NAME(DxbcOpcode::IEq);
ENUM_NAME(DxbcOpcode::IGe);
ENUM_NAME(DxbcOpcode::ILt);
ENUM_NAME(DxbcOpcode::IMad);
ENUM_NAME(DxbcOpcode::IMax);
ENUM_NAME(DxbcOpcode::IMin);
ENUM_NAME(DxbcOpcode::IMul);
ENUM_NAME(DxbcOpcode::INe);
ENUM_NAME(DxbcOpcode::INeg);
ENUM_NAME(DxbcOpcode::IShl);
ENUM_NAME(DxbcOpcode::IShr);
ENUM_NAME(DxbcOpcode::ItoF);
ENUM_NAME(DxbcOpcode::Label);
ENUM_NAME(DxbcOpcode::Ld);
ENUM_NAME(DxbcOpcode::LdMs);
ENUM_NAME(DxbcOpcode::Log);
ENUM_NAME(DxbcOpcode::Loop);
ENUM_NAME(DxbcOpcode::Lt);
ENUM_NAME(DxbcOpcode::Mad);
ENUM_NAME(DxbcOpcode::Min);
ENUM_NAME(DxbcOpcode::Max);
ENUM_NAME(DxbcOpcode::CustomData);
ENUM_NAME(DxbcOpcode::Mov);
ENUM_NAME(DxbcOpcode::Movc);
ENUM_NAME(DxbcOpcode::Mul);
ENUM_NAME(DxbcOpcode::Ne);
ENUM_NAME(DxbcOpcode::Nop);
ENUM_NAME(DxbcOpcode::Not);
ENUM_NAME(DxbcOpcode::Or);
ENUM_NAME(DxbcOpcode::ResInfo);
ENUM_NAME(DxbcOpcode::Ret);
ENUM_NAME(DxbcOpcode::Retc);
ENUM_NAME(DxbcOpcode::RoundNe);
ENUM_NAME(DxbcOpcode::RoundNi);
ENUM_NAME(DxbcOpcode::RoundPi);
ENUM_NAME(DxbcOpcode::RoundZ);
ENUM_NAME(DxbcOpcode::Rsq);
ENUM_NAME(DxbcOpcode::Sample);
ENUM_NAME(DxbcOpcode::SampleC);
ENUM_NAME(DxbcOpcode::SampleClz);
ENUM_NAME(DxbcOpcode::SampleL);
ENUM_NAME(DxbcOpcode::SampleD);
ENUM_NAME(DxbcOpcode::SampleB);
ENUM_NAME(DxbcOpcode::Sqrt);
ENUM_NAME(DxbcOpcode::Switch);
ENUM_NAME(DxbcOpcode::SinCos);
ENUM_NAME(DxbcOpcode::UDiv);
ENUM_NAME(DxbcOpcode::ULt);
ENUM_NAME(DxbcOpcode::UGe);
ENUM_NAME(DxbcOpcode::UMul);
ENUM_NAME(DxbcOpcode::UMad);
ENUM_NAME(DxbcOpcode::UMax);
ENUM_NAME(DxbcOpcode::UMin);
ENUM_NAME(DxbcOpcode::UShr);
ENUM_NAME(DxbcOpcode::UtoF);
ENUM_NAME(DxbcOpcode::Xor);
ENUM_NAME(DxbcOpcode::DclResource);
ENUM_NAME(DxbcOpcode::DclConstantBuffer);
ENUM_NAME(DxbcOpcode::DclSampler);
ENUM_NAME(DxbcOpcode::DclIndexRange);
ENUM_NAME(DxbcOpcode::DclGsOutputPrimitiveTopology);
ENUM_NAME(DxbcOpcode::DclGsInputPrimitive);
ENUM_NAME(DxbcOpcode::DclMaxOutputVertexCount);
ENUM_NAME(DxbcOpcode::DclInput);
ENUM_NAME(DxbcOpcode::DclInputSgv);
ENUM_NAME(DxbcOpcode::DclInputSiv);
ENUM_NAME(DxbcOpcode::DclInputPs);
ENUM_NAME(DxbcOpcode::DclInputPsSgv);
ENUM_NAME(DxbcOpcode::DclInputPsSiv);
ENUM_NAME(DxbcOpcode::DclOutput);
ENUM_NAME(DxbcOpcode::DclOutputSgv);
ENUM_NAME(DxbcOpcode::DclOutputSiv);
ENUM_NAME(DxbcOpcode::DclTemps);
ENUM_NAME(DxbcOpcode::DclIndexableTemp);
ENUM_NAME(DxbcOpcode::DclGlobalFlags);
ENUM_NAME(DxbcOpcode::Reserved0);
ENUM_NAME(DxbcOpcode::Lod);
ENUM_NAME(DxbcOpcode::Gather4);
ENUM_NAME(DxbcOpcode::SamplePos);
ENUM_NAME(DxbcOpcode::SampleInfo);
ENUM_NAME(DxbcOpcode::Reserved1);
ENUM_NAME(DxbcOpcode::HsDecls);
ENUM_NAME(DxbcOpcode::HsControlPointPhase);
ENUM_NAME(DxbcOpcode::HsForkPhase);
ENUM_NAME(DxbcOpcode::HsJoinPhase);
ENUM_NAME(DxbcOpcode::EmitStream);
ENUM_NAME(DxbcOpcode::CutStream);
ENUM_NAME(DxbcOpcode::EmitThenCutStream);
ENUM_NAME(DxbcOpcode::InterfaceCall);
ENUM_NAME(DxbcOpcode::BufInfo);
ENUM_NAME(DxbcOpcode::DerivRtxCoarse);
ENUM_NAME(DxbcOpcode::DerivRtxFine);
ENUM_NAME(DxbcOpcode::DerivRtyCoarse);
ENUM_NAME(DxbcOpcode::DerivRtyFine);
ENUM_NAME(DxbcOpcode::Gather4C);
ENUM_NAME(DxbcOpcode::Gather4Po);
ENUM_NAME(DxbcOpcode::Gather4PoC);
ENUM_NAME(DxbcOpcode::Rcp);
ENUM_NAME(DxbcOpcode::F32toF16);
ENUM_NAME(DxbcOpcode::F16toF32);
ENUM_NAME(DxbcOpcode::UAddc);
ENUM_NAME(DxbcOpcode::USubb);
ENUM_NAME(DxbcOpcode::CountBits);
ENUM_NAME(DxbcOpcode::FirstBitHi);
ENUM_NAME(DxbcOpcode::FirstBitLo);
ENUM_NAME(DxbcOpcode::FirstBitShi);
ENUM_NAME(DxbcOpcode::UBfe);
ENUM_NAME(DxbcOpcode::IBfe);
ENUM_NAME(DxbcOpcode::Bfi);
ENUM_NAME(DxbcOpcode::BfRev);
ENUM_NAME(DxbcOpcode::Swapc);
ENUM_NAME(DxbcOpcode::DclStream);
ENUM_NAME(DxbcOpcode::DclFunctionBody);
ENUM_NAME(DxbcOpcode::DclFunctionTable);
ENUM_NAME(DxbcOpcode::DclInterface);
ENUM_NAME(DxbcOpcode::DclInputControlPointCount);
ENUM_NAME(DxbcOpcode::DclOutputControlPointCount);
ENUM_NAME(DxbcOpcode::DclTessDomain);
ENUM_NAME(DxbcOpcode::DclTessPartitioning);
ENUM_NAME(DxbcOpcode::DclTessOutputPrimitive);
ENUM_NAME(DxbcOpcode::DclHsMaxTessFactor);
ENUM_NAME(DxbcOpcode::DclHsForkPhaseInstanceCount);
ENUM_NAME(DxbcOpcode::DclHsJoinPhaseInstanceCount);
ENUM_NAME(DxbcOpcode::DclThreadGroup);
ENUM_NAME(DxbcOpcode::DclUavTyped);
ENUM_NAME(DxbcOpcode::DclUavRaw);
ENUM_NAME(DxbcOpcode::DclUavStructured);
ENUM_NAME(DxbcOpcode::DclThreadGroupSharedMemoryRaw);
ENUM_NAME(DxbcOpcode::DclThreadGroupSharedMemoryStructured);
ENUM_NAME(DxbcOpcode::DclResourceRaw);
ENUM_NAME(DxbcOpcode::DclResourceStructured);
ENUM_NAME(DxbcOpcode::LdUavTyped);
ENUM_NAME(DxbcOpcode::StoreUavTyped);
ENUM_NAME(DxbcOpcode::LdRaw);
ENUM_NAME(DxbcOpcode::StoreRaw);
ENUM_NAME(DxbcOpcode::LdStructured);
ENUM_NAME(DxbcOpcode::StoreStructured);
ENUM_NAME(DxbcOpcode::AtomicAnd);
ENUM_NAME(DxbcOpcode::AtomicOr);
ENUM_NAME(DxbcOpcode::AtomicXor);
ENUM_NAME(DxbcOpcode::AtomicCmpStore);
ENUM_NAME(DxbcOpcode::AtomicIAdd);
ENUM_NAME(DxbcOpcode::AtomicIMax);
ENUM_NAME(DxbcOpcode::AtomicIMin);
ENUM_NAME(DxbcOpcode::AtomicUMax);
ENUM_NAME(DxbcOpcode::AtomicUMin);
ENUM_NAME(DxbcOpcode::ImmAtomicAlloc);
ENUM_NAME(DxbcOpcode::ImmAtomicConsume);
ENUM_NAME(DxbcOpcode::ImmAtomicIAdd);
ENUM_NAME(DxbcOpcode::ImmAtomicAnd);
ENUM_NAME(DxbcOpcode::ImmAtomicOr);
ENUM_NAME(DxbcOpcode::ImmAtomicXor);
ENUM_NAME(DxbcOpcode::ImmAtomicExch);
ENUM_NAME(DxbcOpcode::ImmAtomicCmpExch);
ENUM_NAME(DxbcOpcode::ImmAtomicIMax);
ENUM_NAME(DxbcOpcode::ImmAtomicIMin);
ENUM_NAME(DxbcOpcode::ImmAtomicUMax);
ENUM_NAME(DxbcOpcode::ImmAtomicUMin);
ENUM_NAME(DxbcOpcode::Sync);
ENUM_NAME(DxbcOpcode::DAdd);
ENUM_NAME(DxbcOpcode::DMax);
ENUM_NAME(DxbcOpcode::DMin);
ENUM_NAME(DxbcOpcode::DMul);
ENUM_NAME(DxbcOpcode::DEq);
ENUM_NAME(DxbcOpcode::DGe);
ENUM_NAME(DxbcOpcode::DLt);
ENUM_NAME(DxbcOpcode::DNe);
ENUM_NAME(DxbcOpcode::DMov);
ENUM_NAME(DxbcOpcode::DMovc);
ENUM_NAME(DxbcOpcode::DtoF);
ENUM_NAME(DxbcOpcode::FtoD);
ENUM_NAME(DxbcOpcode::EvalSnapped);
ENUM_NAME(DxbcOpcode::EvalSampleIndex);
ENUM_NAME(DxbcOpcode::EvalCentroid);
ENUM_NAME(DxbcOpcode::DclGsInstanceCount);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcExtOpcode e) {
switch (e) {
ENUM_NAME(DxbcExtOpcode::Empty);
ENUM_NAME(DxbcExtOpcode::SampleControls);
ENUM_NAME(DxbcExtOpcode::ResourceDim);
ENUM_NAME(DxbcExtOpcode::ResourceReturnType);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcOperandType e) {
switch (e) {
ENUM_NAME(DxbcOperandType::Temp);
ENUM_NAME(DxbcOperandType::Input);
ENUM_NAME(DxbcOperandType::Output);
ENUM_NAME(DxbcOperandType::IndexableTemp);
ENUM_NAME(DxbcOperandType::Imm32);
ENUM_NAME(DxbcOperandType::Imm64);
ENUM_NAME(DxbcOperandType::Sampler);
ENUM_NAME(DxbcOperandType::Resource);
ENUM_NAME(DxbcOperandType::ConstantBuffer);
ENUM_NAME(DxbcOperandType::ImmediateConstantBuffer);
ENUM_NAME(DxbcOperandType::Label);
ENUM_NAME(DxbcOperandType::InputPrimitiveId);
ENUM_NAME(DxbcOperandType::OutputDepth);
ENUM_NAME(DxbcOperandType::Null);
ENUM_NAME(DxbcOperandType::Rasterizer);
ENUM_NAME(DxbcOperandType::OutputCoverageMask);
ENUM_NAME(DxbcOperandType::Stream);
ENUM_NAME(DxbcOperandType::FunctionBody);
ENUM_NAME(DxbcOperandType::FunctionTable);
ENUM_NAME(DxbcOperandType::Interface);
ENUM_NAME(DxbcOperandType::FunctionInput);
ENUM_NAME(DxbcOperandType::FunctionOutput);
ENUM_NAME(DxbcOperandType::OutputControlPointId);
ENUM_NAME(DxbcOperandType::InputForkInstanceId);
ENUM_NAME(DxbcOperandType::InputJoinInstanceId);
ENUM_NAME(DxbcOperandType::InputControlPoint);
ENUM_NAME(DxbcOperandType::OutputControlPoint);
ENUM_NAME(DxbcOperandType::InputPatchConstant);
ENUM_NAME(DxbcOperandType::InputDomainPoint);
ENUM_NAME(DxbcOperandType::ThisPointer);
ENUM_NAME(DxbcOperandType::UnorderedAccessView);
ENUM_NAME(DxbcOperandType::ThreadGroupSharedMemory);
ENUM_NAME(DxbcOperandType::InputThreadId);
ENUM_NAME(DxbcOperandType::InputThreadGroupId);
ENUM_NAME(DxbcOperandType::InputThreadIdInGroup);
ENUM_NAME(DxbcOperandType::InputCoverageMask);
ENUM_NAME(DxbcOperandType::InputThreadIndexInGroup);
ENUM_NAME(DxbcOperandType::InputGsInstanceId);
ENUM_NAME(DxbcOperandType::OutputDepthGe);
ENUM_NAME(DxbcOperandType::OutputDepthLe);
ENUM_NAME(DxbcOperandType::CycleCounter);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, dxvk::DxbcOperandExt e) {
switch (e) {
ENUM_NAME(DxbcOperandExt::OperandModifier);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcComponentCount e) {
switch (e) {
ENUM_NAME(DxbcComponentCount::Component0);
ENUM_NAME(DxbcComponentCount::Component1);
ENUM_NAME(DxbcComponentCount::Component4);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcRegMode e) {
switch (e) {
ENUM_NAME(DxbcRegMode::Mask);
ENUM_NAME(DxbcRegMode::Swizzle);
ENUM_NAME(DxbcRegMode::Select1);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcOperandIndexRepresentation e) {
switch (e) {
ENUM_NAME(DxbcOperandIndexRepresentation::Imm32);
ENUM_NAME(DxbcOperandIndexRepresentation::Imm64);
ENUM_NAME(DxbcOperandIndexRepresentation::Relative);
ENUM_NAME(DxbcOperandIndexRepresentation::Imm32Relative);
ENUM_NAME(DxbcOperandIndexRepresentation::Imm64Relative);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcResourceDim e) {
switch (e) {
ENUM_NAME(DxbcResourceDim::Unknown);
ENUM_NAME(DxbcResourceDim::Buffer);
ENUM_NAME(DxbcResourceDim::Texture1D);
ENUM_NAME(DxbcResourceDim::Texture2D);
ENUM_NAME(DxbcResourceDim::Texture2DMs);
ENUM_NAME(DxbcResourceDim::Texture3D);
ENUM_NAME(DxbcResourceDim::TextureCube);
ENUM_NAME(DxbcResourceDim::Texture1DArr);
ENUM_NAME(DxbcResourceDim::Texture2DArr);
ENUM_NAME(DxbcResourceDim::Texture2DMsArr);
ENUM_NAME(DxbcResourceDim::TextureCubeArr);
ENUM_NAME(DxbcResourceDim::RawBuffer);
ENUM_NAME(DxbcResourceDim::StructuredBuffer);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcResourceReturnType e) {
switch (e) {
ENUM_NAME(DxbcResourceReturnType::Unorm);
ENUM_NAME(DxbcResourceReturnType::Snorm);
ENUM_NAME(DxbcResourceReturnType::Sint);
ENUM_NAME(DxbcResourceReturnType::Uint);
ENUM_NAME(DxbcResourceReturnType::Float);
ENUM_NAME(DxbcResourceReturnType::Mixed);
ENUM_NAME(DxbcResourceReturnType::Double);
ENUM_NAME(DxbcResourceReturnType::Continued);
ENUM_NAME(DxbcResourceReturnType::Unused);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcRegisterComponentType e) {
switch (e) {
ENUM_NAME(DxbcRegisterComponentType::Unknown);
ENUM_NAME(DxbcRegisterComponentType::Uint32);
ENUM_NAME(DxbcRegisterComponentType::Sint32);
ENUM_NAME(DxbcRegisterComponentType::Float32);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcInstructionReturnType e) {
switch (e) {
ENUM_NAME(DxbcInstructionReturnType::Float);
ENUM_NAME(DxbcInstructionReturnType::Uint);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, DxbcSystemValue e) {
switch (e) {
ENUM_NAME(DxbcSystemValue::None);
ENUM_NAME(DxbcSystemValue::Position);
ENUM_NAME(DxbcSystemValue::ClipDistance);
ENUM_NAME(DxbcSystemValue::CullDistance);
ENUM_NAME(DxbcSystemValue::RenderTargetId);
ENUM_NAME(DxbcSystemValue::ViewportId);
ENUM_NAME(DxbcSystemValue::VertexId);
ENUM_NAME(DxbcSystemValue::PrimitiveId);
ENUM_NAME(DxbcSystemValue::InstanceId);
ENUM_NAME(DxbcSystemValue::IsFrontFace);
ENUM_NAME(DxbcSystemValue::SampleIndex);
ENUM_NAME(DxbcSystemValue::FinalQuadUeq0EdgeTessFactor);
ENUM_NAME(DxbcSystemValue::FinalQuadVeq0EdgeTessFactor);
ENUM_NAME(DxbcSystemValue::FinalQuadUeq1EdgeTessFactor);
ENUM_NAME(DxbcSystemValue::FinalQuadVeq1EdgeTessFactor);
ENUM_NAME(DxbcSystemValue::FinalQuadUInsideTessFactor);
ENUM_NAME(DxbcSystemValue::FinalQuadVInsideTessFactor);
ENUM_NAME(DxbcSystemValue::FinalTriUeq0EdgeTessFactor);
ENUM_NAME(DxbcSystemValue::FinalTriVeq0EdgeTessFactor);
ENUM_NAME(DxbcSystemValue::FinalTriWeq0EdgeTessFactor);
ENUM_NAME(DxbcSystemValue::FinalTriInsideTessFactor);
ENUM_NAME(DxbcSystemValue::FinalLineDetailTessFactor);
ENUM_NAME(DxbcSystemValue::FinalLineDensityTessFactor);
ENUM_NAME(DxbcSystemValue::Target);
ENUM_NAME(DxbcSystemValue::Depth);
ENUM_NAME(DxbcSystemValue::Coverage);
ENUM_NAME(DxbcSystemValue::DepthGe);
ENUM_NAME(DxbcSystemValue::DepthLe);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, dxvk::DxbcProgramType e) {
switch (e) {
ENUM_NAME(DxbcProgramType::PixelShader);
ENUM_NAME(DxbcProgramType::VertexShader);
ENUM_NAME(DxbcProgramType::GeometryShader);
ENUM_NAME(DxbcProgramType::HullShader);
ENUM_NAME(DxbcProgramType::DomainShader);
ENUM_NAME(DxbcProgramType::ComputeShader);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, dxvk::DxbcCustomDataClass e) {
switch (e) {
ENUM_NAME(DxbcCustomDataClass::Comment);
ENUM_NAME(DxbcCustomDataClass::DebugInfo);
ENUM_NAME(DxbcCustomDataClass::Opaque);
ENUM_NAME(DxbcCustomDataClass::ImmConstBuf);
ENUM_DEFAULT(e);
}
}
std::ostream& operator << (std::ostream& os, dxvk::DxbcScalarType e) {
switch (e) {
ENUM_NAME(DxbcScalarType::Uint32);
ENUM_NAME(DxbcScalarType::Uint64);
ENUM_NAME(DxbcScalarType::Sint32);
ENUM_NAME(DxbcScalarType::Sint64);
ENUM_NAME(DxbcScalarType::Float32);
ENUM_NAME(DxbcScalarType::Float64);
ENUM_NAME(DxbcScalarType::Bool);
ENUM_DEFAULT(e);
}
}
} //namespace dxvk

26
src/dxbc/dxbc_names.h Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <ostream>
#include "dxbc_common.h"
#include "dxbc_enums.h"
namespace dxvk {
std::ostream& operator << (std::ostream& os, DxbcOpcode e);
std::ostream& operator << (std::ostream& os, DxbcExtOpcode e);
std::ostream& operator << (std::ostream& os, DxbcOperandType e);
std::ostream& operator << (std::ostream& os, DxbcOperandExt e);
std::ostream& operator << (std::ostream& os, DxbcComponentCount e);
std::ostream& operator << (std::ostream& os, DxbcRegMode e);
std::ostream& operator << (std::ostream& os, DxbcOperandIndexRepresentation e);
std::ostream& operator << (std::ostream& os, DxbcResourceDim e);
std::ostream& operator << (std::ostream& os, DxbcResourceReturnType e);
std::ostream& operator << (std::ostream& os, DxbcRegisterComponentType e);
std::ostream& operator << (std::ostream& os, DxbcInstructionReturnType e);
std::ostream& operator << (std::ostream& os, DxbcSystemValue e);
std::ostream& operator << (std::ostream& os, DxbcProgramType e);
std::ostream& operator << (std::ostream& os, DxbcCustomDataClass e);
std::ostream& operator << (std::ostream& os, DxbcScalarType e);
} // namespace dxvk

60
src/dxbc/dxbc_options.cpp Normal file
View file

@ -0,0 +1,60 @@
//#include "../d3d11/d3d11_options.h"
#include "dxbc_options.h"
namespace dxvk {
DxbcOptions::DxbcOptions() {
}
DxbcOptions::DxbcOptions(/*const Rc<DxvkDevice>& device, */const D3D11Options& options) {
/*const Rc<DxvkAdapter> adapter = device->adapter();
const DxvkDeviceFeatures& devFeatures = device->features();
const DxvkDeviceInfo& devInfo = adapter->devicePropertiesExt();
useDepthClipWorkaround
= !devFeatures.extDepthClipEnable.depthClipEnable;
useSubgroupOpsForAtomicCounters
= (devInfo.vk11.subgroupSupportedStages & VK_SHADER_STAGE_COMPUTE_BIT)
&& (devInfo.vk11.subgroupSupportedOperations & VK_SUBGROUP_FEATURE_BALLOT_BIT);
VkFormatFeatureFlags2 r32Features
= device->getFormatFeatures(VK_FORMAT_R32_SFLOAT).optimal
& device->getFormatFeatures(VK_FORMAT_R32_UINT).optimal
& device->getFormatFeatures(VK_FORMAT_R32_SINT).optimal;
supportsTypedUavLoadR32 = (r32Features & VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT);
switch (device->config().useRawSsbo) {
case Tristate::Auto: minSsboAlignment = devInfo.core.properties.limits.minStorageBufferOffsetAlignment; break;
case Tristate::True: minSsboAlignment = 4u; break;
case Tristate::False: minSsboAlignment = ~0u; break;
}
invariantPosition = options.invariantPosition;
zeroInitWorkgroupMemory = options.zeroInitWorkgroupMemory;
forceVolatileTgsmAccess = options.forceVolatileTgsmAccess;
disableMsaa = options.disableMsaa;
forceSampleRateShading = options.forceSampleRateShading;
enableSampleShadingInterlock = device->features().extFragmentShaderInterlock.fragmentShaderSampleInterlock;
// Figure out float control flags to match D3D11 rules
if (options.floatControls) {
if (devInfo.vk12.shaderSignedZeroInfNanPreserveFloat32)
floatControl.set(DxbcFloatControlFlag::PreserveNan32);
if (devInfo.vk12.shaderSignedZeroInfNanPreserveFloat64)
floatControl.set(DxbcFloatControlFlag::PreserveNan64);
if (devInfo.vk12.denormBehaviorIndependence != VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE) {
if (devInfo.vk12.shaderDenormFlushToZeroFloat32)
floatControl.set(DxbcFloatControlFlag::DenormFlushToZero32);
if (devInfo.vk12.shaderDenormPreserveFloat64)
floatControl.set(DxbcFloatControlFlag::DenormPreserve64);
}
}*/
}
}

63
src/dxbc/dxbc_options.h Normal file
View file

@ -0,0 +1,63 @@
#pragma once
#include <vulkan/vulkan.h>
#include "util_flags.h"
#include <cstdint>
namespace dxvk {
struct D3D11Options;
enum class DxbcFloatControlFlag : uint32_t {
DenormFlushToZero32,
DenormPreserve64,
PreserveNan32,
PreserveNan64,
};
using DxbcFloatControlFlags = Flags<DxbcFloatControlFlag>;
struct DxbcOptions {
DxbcOptions();
DxbcOptions(const D3D11Options& options);
// Clamp oDepth in fragment shaders if the depth
// clip device feature is not supported
bool useDepthClipWorkaround = false;
/// Determines whether format qualifiers
/// on typed UAV loads are required
bool supportsTypedUavLoadR32 = false;
/// Use subgroup operations to reduce the number of
/// atomic operations for append/consume buffers.
bool useSubgroupOpsForAtomicCounters = false;
/// Clear thread-group shared memory to zero
bool zeroInitWorkgroupMemory = false;
/// Declare vertex positions as invariant
bool invariantPosition = false;
/// Insert memory barriers after TGSM stoes
bool forceVolatileTgsmAccess = false;
/// Replace ld_ms with ld
bool disableMsaa = false;
/// Force sample rate shading by using sample
/// interpolation for fragment shader inputs
bool forceSampleRateShading = false;
// Enable per-sample interlock if supported
bool enableSampleShadingInterlock = false;
/// Float control flags
DxbcFloatControlFlags floatControl;
/// Minimum storage buffer alignment
VkDeviceSize minSsboAlignment = 0;
};
}

58
src/dxbc/dxbc_reader.cpp Normal file
View file

@ -0,0 +1,58 @@
#include <cstring>
#include "dxbc_reader.h"
namespace dxvk {
DxbcTag DxbcReader::readTag() {
DxbcTag tag;
this->read(&tag, 4);
return tag;
}
std::string DxbcReader::readString() {
std::string result;
while (m_data[m_pos] != '\0')
result.push_back(m_data[m_pos++]);
m_pos++;
return result;
}
void DxbcReader::read(void* dst, size_t n) {
if (m_pos + n > m_size)
throw DxvkError("DxbcReader::read: Unexpected end of file");
std::memcpy(dst, m_data + m_pos, n);
m_pos += n;
}
void DxbcReader::skip(size_t n) {
if (m_pos + n > m_size)
throw DxvkError("DxbcReader::skip: Unexpected end of file");
m_pos += n;
}
DxbcReader DxbcReader::clone(size_t pos) const {
if (pos > m_size)
throw DxvkError("DxbcReader::clone: Invalid offset");
return DxbcReader(m_data + pos, m_size - pos);
}
DxbcReader DxbcReader::resize(size_t size) const {
if (size > m_size)
throw DxvkError("DxbcReader::resize: Invalid size");
return DxbcReader(m_data, size, m_pos);
}
void DxbcReader::store(std::ostream&& stream) const {
stream.write(m_data, m_size);
}
}

78
src/dxbc/dxbc_reader.h Normal file
View file

@ -0,0 +1,78 @@
#pragma once
#include <cstddef>
#include <string>
#include "dxbc_tag.h"
namespace dxvk {
/**
* \brief DXBC bytecode reader
*
* Holds references to the shader byte code and
* provides methods to read
*/
class DxbcReader {
public:
DxbcReader(const char* data, size_t size)
: DxbcReader(data, size, 0) { }
auto readu8 () { return this->readNum<uint8_t> (); }
auto readu16() { return this->readNum<uint16_t>(); }
auto readu32() { return this->readNum<uint32_t>(); }
auto readu64() { return this->readNum<uint64_t>(); }
auto readi8 () { return this->readNum<int8_t> (); }
auto readi16() { return this->readNum<int16_t> (); }
auto readi32() { return this->readNum<int32_t> (); }
auto readi64() { return this->readNum<int64_t> (); }
auto readf32() { return this->readNum<float> (); }
auto readf64() { return this->readNum<double> (); }
template<typename T>
auto readEnum() {
using Tx = std::underlying_type_t<T>;
return static_cast<T>(this->readNum<Tx>());
}
DxbcTag readTag();
std::string readString();
void read(void* dst, size_t n);
void skip(size_t n);
DxbcReader clone(size_t pos) const;
DxbcReader resize(size_t size) const;
bool eof() const {
return m_pos >= m_size;
}
void store(std::ostream&& stream) const;
private:
DxbcReader(const char* data, size_t size, size_t pos)
: m_data(data), m_size(size), m_pos(pos) { }
const char* m_data = nullptr;
size_t m_size = 0;
size_t m_pos = 0;
template<typename T>
T readNum() {
T result;
this->read(&result, sizeof(result));
return result;
}
};
}

47
src/dxbc/dxbc_tag.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include "dxbc_include.h"
namespace dxvk {
/**
* \brief Four-character tag
*
* Used to identify chunks in the
* compiled DXBC file by name.
*/
class DxbcTag {
public:
DxbcTag() {
for (size_t i = 0; i < 4; i++)
m_chars[i] = '\0';
}
DxbcTag(const char* tag) {
for (size_t i = 0; i < 4; i++)
m_chars[i] = tag[i];
}
bool operator == (const DxbcTag& other) const {
bool result = true;
for (size_t i = 0; i < 4; i++)
result &= m_chars[i] == other.m_chars[i];
return result;
}
bool operator != (const DxbcTag& other) const {
return !this->operator == (other);
}
const char* operator & () const { return m_chars; }
char* operator & () { return m_chars; }
private:
char m_chars[4];
};
}

26
src/dxbc/dxbc_util.cpp Normal file
View file

@ -0,0 +1,26 @@
#include "dxbc_util.h"
namespace dxvk {
uint32_t primitiveVertexCount(DxbcPrimitive primitive) {
static const std::array<uint32_t, 8> s_vertexCounts = {
0, // Undefined
1, // Point
2, // Line
3, // Triangle
0, // Undefined
0, // Undefined
4, // Line with adjacency
6, // Triangle with adjacency
};
if (primitive >= DxbcPrimitive::Patch1) {
return uint32_t(primitive)
- uint32_t(DxbcPrimitive::Patch1)
+ 1u;
} else {
return s_vertexCounts.at(uint32_t(primitive));
}
}
}

127
src/dxbc/dxbc_util.h Normal file
View file

@ -0,0 +1,127 @@
#pragma once
#include "dxbc_common.h"
#include "dxbc_enums.h"
namespace dxvk {
/**
* \brief Push constant struct
*/
struct DxbcPushConstants {
uint32_t rasterizerSampleCount;
};
/**
* \brief Binding numbers and properties
*/
enum DxbcBindingProperties : uint32_t {
DxbcConstBufBindingIndex = 0,
DxbcConstBufBindingCount = 16,
DxbcSamplerBindingIndex = DxbcConstBufBindingIndex
+ DxbcConstBufBindingCount,
DxbcSamplerBindingCount = 16,
DxbcResourceBindingIndex = DxbcSamplerBindingIndex
+ DxbcSamplerBindingCount,
DxbcResourceBindingCount = 128,
DxbcStageBindingCount = DxbcConstBufBindingCount
+ DxbcSamplerBindingCount
+ DxbcResourceBindingCount,
DxbcUavBindingIndex = DxbcStageBindingCount * 6,
DxbcUavBindingCount = 64,
};
/**
* \brief Computes first binding index for a given stage
*
* \param [in] stage The shader stage
* \returns Index of first binding
*/
inline uint32_t computeStageBindingOffset(DxbcProgramType stage) {
return DxbcStageBindingCount * uint32_t(stage);
}
/**
* \brief Computes first UAV binding index offset for a given stage
*
* \param [in] stage The shader stage
* \returns Index of first UAV binding
*/
inline uint32_t computeStageUavBindingOffset(DxbcProgramType stage) {
return DxbcUavBindingIndex
+ DxbcUavBindingCount * (stage == DxbcProgramType::ComputeShader ? 2 : 0);
}
/**
* \brief Computes constant buffer binding index
*
* \param [in] stage Shader stage
* \param [in] index Constant buffer index
* \returns Binding index
*/
inline uint32_t computeConstantBufferBinding(DxbcProgramType stage, uint32_t index) {
return computeStageBindingOffset(stage) + DxbcConstBufBindingIndex + index;
}
/**
* \brief Computes sampler binding index
*
* \param [in] stage Shader stage
* \param [in] index Sampler index
* \returns Binding index
*/
inline uint32_t computeSamplerBinding(DxbcProgramType stage, uint32_t index) {
return computeStageBindingOffset(stage) + DxbcSamplerBindingIndex + index;
}
/**
* \brief Computes resource binding index
*
* \param [in] stage Shader stage
* \param [in] index Resource index
* \returns Binding index
*/
inline uint32_t computeSrvBinding(DxbcProgramType stage, uint32_t index) {
return computeStageBindingOffset(stage) + DxbcResourceBindingIndex + index;
}
/**
* \brief Computes UAV binding offset
*
* \param [in] stage Shader stage
* \param [in] index UAV index
* \returns Binding index
*/
inline uint32_t computeUavBinding(DxbcProgramType stage, uint32_t index) {
return computeStageUavBindingOffset(stage) + index;
}
/**
* \brief Computes UAV counter binding offset
*
* \param [in] stage Shader stage
* \param [in] index UAV index
* \returns Binding index
*/
inline uint32_t computeUavCounterBinding(DxbcProgramType stage, uint32_t index) {
return computeStageUavBindingOffset(stage) + DxbcUavBindingCount + index;
}
/**
* \brief Primitive vertex count
*
* Calculates the number of vertices
* for a given primitive type.
*/
uint32_t primitiveVertexCount(
DxbcPrimitive primitive);
}

607
src/dxbc/dxvk_shader.h Normal file
View file

@ -0,0 +1,607 @@
#pragma once
#include <vector>
#include "dxvk_include.h"
#include "dxvk_limits.h"
#include "dxvk_pipelayout.h"
#include "dxvk_shader_key.h"
#include "../spirv/spirv_code_buffer.h"
#include "../spirv/spirv_compression.h"
#include "../spirv/spirv_module.h"
namespace dxvk {
class DxvkShader;
class DxvkShaderModule;
class DxvkPipelineManager;
struct DxvkPipelineStats;
/**
* \brief Shader flags
*
* Provides extra information about the features
* used by a shader.
*/
enum DxvkShaderFlag : uint64_t {
HasSampleRateShading,
HasTransformFeedback,
ExportsPosition,
ExportsStencilRef,
ExportsViewportIndexLayerFromVertexStage,
ExportsSampleMask,
UsesFragmentCoverage,
UsesSparseResidency,
};
using DxvkShaderFlags = Flags<DxvkShaderFlag>;
/**
* \brief Shader info
*/
struct DxvkShaderCreateInfo {
/// Shader stage
VkShaderStageFlagBits stage;
/// Descriptor info
uint32_t bindingCount = 0;
const DxvkBindingInfo* bindings = nullptr;
/// Input and output register mask
uint32_t inputMask = 0;
uint32_t outputMask = 0;
/// Flat shading input mask
uint32_t flatShadingInputs = 0;
/// Push constant range
uint32_t pushConstOffset = 0;
uint32_t pushConstSize = 0;
/// Uniform buffer data
uint32_t uniformSize = 0;
const char* uniformData = nullptr;
/// Rasterized stream, or -1
int32_t xfbRasterizedStream = 0;
/// Tess control patch vertex count
uint32_t patchVertexCount = 0;
/// Transform feedback vertex strides
uint32_t xfbStrides[MaxNumXfbBuffers] = { };
/// Output primitive topology
VkPrimitiveTopology outputTopology = VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
};
/**
* \brief Shader module create info
*/
struct DxvkShaderModuleCreateInfo {
bool fsDualSrcBlend = false;
bool fsFlatShading = false;
uint32_t undefinedInputs = 0;
std::array<VkComponentMapping, MaxNumRenderTargets> rtSwizzles = { };
bool eq(const DxvkShaderModuleCreateInfo& other) const;
size_t hash() const;
};
/**
* \brief Shader object
*
* Stores a SPIR-V shader and information on the
* bindings that the shader uses. In order to use
* the shader with a pipeline, a shader module
* needs to be created from he shader object.
*/
class DxvkShader : public RcObject {
public:
DxvkShader(
const DxvkShaderCreateInfo& info,
SpirvCodeBuffer&& spirv);
~DxvkShader();
/**
* \brief Shader info
* \returns Shader info
*/
const DxvkShaderCreateInfo& info() const {
return m_info;
}
/**
* \brief Retrieves shader flags
* \returns Shader flags
*/
DxvkShaderFlags flags() const {
return m_flags;
}
/**
* \brief Retrieves binding layout
* \returns Binding layout
*/
const DxvkBindingLayout& getBindings() const {
return m_bindings;
}
/**
* \brief Retrieves spec constant mask
* \returns Bit mask of used spec constants
*/
uint32_t getSpecConstantMask() const {
return m_specConstantMask;
}
/**
* \brief Tests whether this shader needs to be compiled
*
* If pipeline libraries are supported, this will return
* \c false once the pipeline library is being compiled.
* \returns \c true if compilation is still needed
*/
bool needsLibraryCompile() const {
return m_needsLibraryCompile.load();
}
/**
* \brief Notifies library compile
*
* Called automatically when pipeline compilation begins.
* Subsequent calls to \ref needsLibraryCompile will return
* \c false.
*/
void notifyLibraryCompile() {
m_needsLibraryCompile.store(false);
}
/**
* \brief Gets raw code without modification
*/
SpirvCodeBuffer getRawCode() const {
return m_code.decompress();
}
/**
* \brief Patches code using given info
*
* Rewrites binding IDs and potentially fixes up other
* parts of the code depending on pipeline state.
* \param [in] layout Biding layout
* \param [in] state Pipeline state info
* \returns Uncompressed SPIR-V code buffer
*/
SpirvCodeBuffer getCode(
const DxvkBindingLayoutObjects* layout,
const DxvkShaderModuleCreateInfo& state) const;
/**
* \brief Tests whether this shader supports pipeline libraries
*
* This is true for any vertex, fragment, or compute shader that does not
* require additional pipeline state to be compiled into something useful.
* \param [in] standalone Set to \c true to evaluate this in the context
* of a single-shader pipeline library, or \c false for a pre-raster
* shader library consisting of multiple shader stages.
* \returns \c true if this shader can be used with pipeline libraries
*/
bool canUsePipelineLibrary(bool standalone) const;
/**
* \brief Dumps SPIR-V shader
*
* Can be used to store the SPIR-V code in a file.
* \param [in] outputStream Stream to write to
*/
void dump(std::ostream& outputStream) const;
/**
* \brief Sets the shader key
* \param [in] key Unique key
*/
void setShaderKey(const DxvkShaderKey& key) {
m_key = key;
m_hash = key.hash();
}
/**
* \brief Retrieves shader key
* \returns The unique shader key
*/
DxvkShaderKey getShaderKey() const {
return m_key;
}
/**
* \brief Get lookup hash
*
* Retrieves a non-unique hash value derived from the
* shader key which can be used to perform lookups.
* This is better than relying on the pointer value.
* \returns Hash value for map lookups
*/
size_t getHash() const {
return m_hash;
}
/**
* \brief Retrieves debug name
* \returns The shader's name
*/
std::string debugName() const {
return m_key.toString();
}
/**
* \brief Get lookup hash for a shader
*
* Convenience method that returns \c 0 for a null
* pointer, and the shader's lookup hash otherwise.
* \param [in] shader The shader
* \returns The shader's lookup hash, or 0
*/
static size_t getHash(const Rc<DxvkShader>& shader) {
return shader != nullptr ? shader->getHash() : 0;
}
private:
struct BindingOffsets {
uint32_t bindingId;
uint32_t bindingOffset;
uint32_t setOffset;
};
DxvkShaderCreateInfo m_info;
SpirvCompressedBuffer m_code;
DxvkShaderFlags m_flags;
DxvkShaderKey m_key;
size_t m_hash = 0;
size_t m_o1IdxOffset = 0;
size_t m_o1LocOffset = 0;
uint32_t m_specConstantMask = 0;
std::atomic<bool> m_needsLibraryCompile = { true };
std::vector<char> m_uniformData;
std::vector<BindingOffsets> m_bindingOffsets;
DxvkBindingLayout m_bindings;
static void eliminateInput(
SpirvCodeBuffer& code,
uint32_t location);
static void emitOutputSwizzles(
SpirvCodeBuffer& code,
uint32_t outputMask,
const VkComponentMapping* swizzles);
static void emitFlatShadingDeclarations(
SpirvCodeBuffer& code,
uint32_t inputMask);
};
/**
* \brief Shader module object
*
* Manages a Vulkan shader module. This will not
* perform any shader compilation. Instead, the
* context will create pipeline objects on the
* fly when executing draw calls.
*/
class DxvkShaderStageInfo {
public:
DxvkShaderStageInfo(const DxvkDevice* device);
DxvkShaderStageInfo (DxvkShaderStageInfo&& other) = delete;
DxvkShaderStageInfo& operator = (DxvkShaderStageInfo&& other) = delete;
~DxvkShaderStageInfo();
/**
* \brief Counts shader stages
* \returns Shader stage count
*/
uint32_t getStageCount() const {
return m_stageCount;
}
/**
* \brief Queries shader stage infos
* \returns Pointer to shader stage infos
*/
const VkPipelineShaderStageCreateInfo* getStageInfos() const {
return m_stageInfos.data();
}
/**
* \brief Adds a shader stage with specialization info
*
* \param [in] stage Shader stage
* \param [in] code SPIR-V code
* \param [in] specinfo Specialization info
*/
void addStage(
VkShaderStageFlagBits stage,
SpirvCodeBuffer&& code,
const VkSpecializationInfo* specInfo);
/**
* \brief Adds stage using a module identifier
*
* \param [in] stage Shader stage
* \param [in] identifier Shader module identifier
* \param [in] specinfo Specialization info
*/
void addStage(
VkShaderStageFlagBits stage,
const VkShaderModuleIdentifierEXT& identifier,
const VkSpecializationInfo* specInfo);
private:
const DxvkDevice* m_device;
struct ShaderModuleIdentifier {
VkPipelineShaderStageModuleIdentifierCreateInfoEXT createInfo;
std::array<uint8_t, VK_MAX_SHADER_MODULE_IDENTIFIER_SIZE_EXT> data;
};
union ShaderModuleInfo {
ShaderModuleIdentifier moduleIdentifier;
VkShaderModuleCreateInfo moduleInfo;
};
std::array<SpirvCodeBuffer, 5> m_codeBuffers;
std::array<ShaderModuleInfo, 5> m_moduleInfos = { };
std::array<VkPipelineShaderStageCreateInfo, 5> m_stageInfos = { };
uint32_t m_stageCount = 0;
};
/**
* \brief Shader pipeline library compile args
*/
struct DxvkShaderPipelineLibraryCompileArgs {
VkBool32 depthClipEnable = VK_TRUE;
bool operator == (const DxvkShaderPipelineLibraryCompileArgs& other) const {
return depthClipEnable == other.depthClipEnable;
}
bool operator != (const DxvkShaderPipelineLibraryCompileArgs& other) const {
return !this->operator == (other);
}
size_t hash() const {
return size_t(depthClipEnable);
}
};
/**
* \brief Shader set
*
* Stores a set of shader pointers
* for use in a pipeline library.
*/
struct DxvkShaderSet {
DxvkShader* vs = nullptr;
DxvkShader* tcs = nullptr;
DxvkShader* tes = nullptr;
DxvkShader* gs = nullptr;
DxvkShader* fs = nullptr;
DxvkShader* cs = nullptr;
};
/**
* \brief Shader identifer set
*
* Stores a set of shader module identifiers
* for use in a pipeline library.
*/
struct DxvkShaderIdentifierSet {
VkShaderModuleIdentifierEXT vs = { VK_STRUCTURE_TYPE_SHADER_MODULE_IDENTIFIER_EXT };
VkShaderModuleIdentifierEXT tcs = { VK_STRUCTURE_TYPE_SHADER_MODULE_IDENTIFIER_EXT };
VkShaderModuleIdentifierEXT tes = { VK_STRUCTURE_TYPE_SHADER_MODULE_IDENTIFIER_EXT };
VkShaderModuleIdentifierEXT gs = { VK_STRUCTURE_TYPE_SHADER_MODULE_IDENTIFIER_EXT };
VkShaderModuleIdentifierEXT fs = { VK_STRUCTURE_TYPE_SHADER_MODULE_IDENTIFIER_EXT };
VkShaderModuleIdentifierEXT cs = { VK_STRUCTURE_TYPE_SHADER_MODULE_IDENTIFIER_EXT };
};
/**
* \brief Shader pipeline library key
*/
class DxvkShaderPipelineLibraryKey {
public:
DxvkShaderPipelineLibraryKey();
~DxvkShaderPipelineLibraryKey();
/**
* \brief Creates shader set from key
* \returns Shader set
*/
DxvkShaderSet getShaderSet() const;
/**
* \brief Generates merged binding layout
* \returns Binding layout
*/
DxvkBindingLayout getBindings() const;
/**
* \brief Adds a shader to the key
*
* Shaders must be added in stage order.
* \param [in] shader Shader to add
*/
void addShader(
const Rc<DxvkShader>& shader);
/**
* \brief Checks wether a pipeline library can be created
* \returns \c true if all added shaders are compatible
*/
bool canUsePipelineLibrary() const;
/**
* \brief Checks for equality
*
* \param [in] other Key to compare to
* \returns \c true if the keys are equal
*/
bool eq(
const DxvkShaderPipelineLibraryKey& other) const;
/**
* \brief Computes key hash
* \returns Key hash
*/
size_t hash() const;
private:
uint32_t m_shaderCount = 0;
VkShaderStageFlags m_shaderStages = 0;
std::array<Rc<DxvkShader>, 4> m_shaders;
};
/**
* \brief Shader pipeline library
*
* Stores a pipeline object for either a complete compute
* pipeline, a pre-rasterization pipeline library consisting
* of a single vertex shader, or a fragment shader pipeline
* library. All state unknown at shader compile time will
* be made dynamic.
*/
class DxvkShaderPipelineLibrary {
public:
DxvkShaderPipelineLibrary(
const DxvkDevice* device,
DxvkPipelineManager* manager,
const DxvkShaderPipelineLibraryKey& key,
const DxvkBindingLayoutObjects* layout);
~DxvkShaderPipelineLibrary();
/**
* \brief Queries shader module identifier
*
* Can be used to compile an optimized pipeline using the same
* shader code, but without having to wait for the pipeline
* library for this shader shader to compile first.
* \param [in] stage Shader stage to query
* \returns Shader module identifier
*/
VkShaderModuleIdentifierEXT getModuleIdentifier(
VkShaderStageFlagBits stage);
/**
* \brief Acquires pipeline handle for the given set of arguments
*
* Either returns an already compiled pipeline library object, or
* performs the compilation step if that has not happened yet.
* Increments the use count by one.
* \param [in] args Compile arguments
* \returns Vulkan pipeline handle
*/
VkPipeline acquirePipelineHandle(
const DxvkShaderPipelineLibraryCompileArgs& args);
/**
* \brief Releases pipeline
*
* Decrements the use count by 1. If the use count reaches 0,
* any previously compiled pipeline library object may be
* destroyed in order to save memory.
*/
void releasePipelineHandle();
/**
* \brief Compiles the pipeline with default arguments
*
* This is meant to be called from a worker thread in
* order to reduce the amount of work done on the app's
* main thread.
*/
void compilePipeline();
private:
const DxvkDevice* m_device;
DxvkPipelineStats* m_stats;
DxvkShaderSet m_shaders;
const DxvkBindingLayoutObjects* m_layout;
dxvk::mutex m_mutex;
VkPipeline m_pipeline = VK_NULL_HANDLE;
VkPipeline m_pipelineNoDepthClip = VK_NULL_HANDLE;
uint32_t m_useCount = 0u;
bool m_compiledOnce = false;
dxvk::mutex m_identifierMutex;
DxvkShaderIdentifierSet m_identifiers;
void destroyShaderPipelinesLocked();
VkPipeline compileShaderPipelineLocked(
const DxvkShaderPipelineLibraryCompileArgs& args);
VkPipeline compileShaderPipeline(
const DxvkShaderPipelineLibraryCompileArgs& args,
VkPipelineCreateFlags flags);
VkPipeline compileVertexShaderPipeline(
const DxvkShaderPipelineLibraryCompileArgs& args,
const DxvkShaderStageInfo& stageInfo,
VkPipelineCreateFlags flags);
VkPipeline compileFragmentShaderPipeline(
const DxvkShaderStageInfo& stageInfo,
VkPipelineCreateFlags flags);
VkPipeline compileComputeShaderPipeline(
const DxvkShaderStageInfo& stageInfo,
VkPipelineCreateFlags flags);
SpirvCodeBuffer getShaderCode(
VkShaderStageFlagBits stage) const;
void generateModuleIdentifierLocked(
VkShaderModuleIdentifierEXT* identifier,
const SpirvCodeBuffer& spirvCode);
VkShaderStageFlags getShaderStages() const;
DxvkShader* getShader(
VkShaderStageFlagBits stage) const;
VkShaderModuleIdentifierEXT* getShaderIdentifier(
VkShaderStageFlagBits stage);
void notifyLibraryCompile() const;
bool canUsePipelineCacheControl() const;
};
}

41
src/dxvk/dxvk_hash.h Normal file
View file

@ -0,0 +1,41 @@
#pragma once
#include <cstddef>
namespace dxvk {
struct DxvkEq {
template<typename T>
size_t operator () (const T& a, const T& b) const {
return a.eq(b);
}
};
struct DxvkHash {
template<typename T>
size_t operator () (const T& object) const {
return object.hash();
}
};
class DxvkHashState {
public:
void add(size_t hash) {
m_value ^= hash + 0x9e3779b9
+ (m_value << 6)
+ (m_value >> 2);
}
operator size_t () const {
return m_value;
}
private:
size_t m_value = 0;
};
}

13
src/spirv/CMakeLists.txt Normal file
View file

@ -0,0 +1,13 @@
add_library(dxbc-spirv STATIC)
target_sources(dxbc-spirv PRIVATE
spirv_code_buffer.cpp
spirv_code_buffer.h
spirv_compression.cpp
spirv_compression.h
spirv_include.h
spirv_instruction.h
spirv_module.cpp
spirv_module.h
)
target_include_directories(dxbc-spirv PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(dxbc-spirv PUBLIC windows-headers)

View file

@ -0,0 +1,154 @@
#include <array>
#include <cstring>
#include "spirv_code_buffer.h"
namespace dxvk {
SpirvCodeBuffer:: SpirvCodeBuffer() { }
SpirvCodeBuffer::~SpirvCodeBuffer() { }
SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size)
: m_ptr(size) {
m_code.resize(size);
}
SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data)
: m_ptr(size) {
m_code.resize(size);
std::memcpy(m_code.data(), data, size * sizeof(uint32_t));
}
SpirvCodeBuffer::SpirvCodeBuffer(std::istream& stream) {
stream.ignore(std::numeric_limits<std::streamsize>::max());
std::streamsize length = stream.gcount();
stream.clear();
stream.seekg(0, std::ios_base::beg);
std::vector<char> buffer(length);
stream.read(buffer.data(), length);
buffer.resize(stream.gcount());
m_code.resize(buffer.size() / sizeof(uint32_t));
std::memcpy(reinterpret_cast<char*>(m_code.data()),
buffer.data(), m_code.size() * sizeof(uint32_t));
m_ptr = m_code.size();
}
uint32_t SpirvCodeBuffer::allocId() {
constexpr size_t BoundIdsOffset = 3;
if (m_code.size() <= BoundIdsOffset)
return 0;
return m_code[BoundIdsOffset]++;
}
void SpirvCodeBuffer::append(const SpirvCodeBuffer& other) {
if (other.size() != 0) {
const size_t size = m_code.size();
m_code.resize(size + other.m_code.size());
uint32_t* dst = this->m_code.data();
const uint32_t* src = other.m_code.data();
std::memcpy(dst + size, src, other.size());
m_ptr += other.m_code.size();
}
}
void SpirvCodeBuffer::putWord(uint32_t word) {
m_code.insert(m_code.begin() + m_ptr, word);
m_ptr += 1;
}
void SpirvCodeBuffer::putIns(spv::Op opCode, uint16_t wordCount) {
this->putWord(
(static_cast<uint32_t>(opCode) << 0)
| (static_cast<uint32_t>(wordCount) << 16));
}
void SpirvCodeBuffer::putInt32(uint32_t word) {
this->putWord(word);
}
void SpirvCodeBuffer::putInt64(uint64_t value) {
this->putWord(value >> 0);
this->putWord(value >> 32);
}
void SpirvCodeBuffer::putFloat32(float value) {
uint32_t tmp;
static_assert(sizeof(tmp) == sizeof(value));
std::memcpy(&tmp, &value, sizeof(value));
this->putInt32(tmp);
}
void SpirvCodeBuffer::putFloat64(double value) {
uint64_t tmp;
static_assert(sizeof(tmp) == sizeof(value));
std::memcpy(&tmp, &value, sizeof(value));
this->putInt64(tmp);
}
void SpirvCodeBuffer::putStr(const char* str) {
uint32_t word = 0;
uint32_t nbit = 0;
for (uint32_t i = 0; str[i] != '\0'; str++) {
word |= (static_cast<uint32_t>(str[i]) & 0xFF) << nbit;
if ((nbit += 8) == 32) {
this->putWord(word);
word = 0;
nbit = 0;
}
}
// Commit current word
this->putWord(word);
}
void SpirvCodeBuffer::putHeader(uint32_t version, uint32_t boundIds) {
this->putWord(spv::MagicNumber);
this->putWord(version);
this->putWord(0); // Generator
this->putWord(boundIds);
this->putWord(0); // Schema
}
void SpirvCodeBuffer::erase(size_t size) {
m_code.erase(
m_code.begin() + m_ptr,
m_code.begin() + m_ptr + size);
}
uint32_t SpirvCodeBuffer::strLen(const char* str) {
// Null-termination plus padding
return (std::strlen(str) + 4) / 4;
}
void SpirvCodeBuffer::store(std::ostream& stream) const {
stream.write(
reinterpret_cast<const char*>(m_code.data()),
sizeof(uint32_t) * m_code.size());
}
}

View file

@ -0,0 +1,228 @@
#pragma once
#include <iostream>
#include <utility>
#include <vector>
#include "spirv_instruction.h"
namespace dxvk {
/**
* \brief SPIR-V code buffer
*
* Helper class for generating SPIR-V shaders.
* Stores arbitrary SPIR-V instructions in a
* format that can be read by Vulkan drivers.
*/
class SpirvCodeBuffer {
public:
SpirvCodeBuffer();
explicit SpirvCodeBuffer(uint32_t size);
SpirvCodeBuffer(const SpirvCodeBuffer &) = default;
SpirvCodeBuffer(SpirvCodeBuffer &&) = default;
SpirvCodeBuffer(uint32_t size, const uint32_t* data);
SpirvCodeBuffer(std::istream& stream);
template<size_t N>
SpirvCodeBuffer(const uint32_t (&data)[N])
: SpirvCodeBuffer(N, data) { }
~SpirvCodeBuffer();
SpirvCodeBuffer &operator=(const SpirvCodeBuffer &) = default;
SpirvCodeBuffer &operator=(SpirvCodeBuffer &&) = default;
/**
* \brief Code data
* \returns Code data
*/
const uint32_t* data() const { return m_code.data(); }
uint32_t* data() { return m_code.data(); }
/**
* \brief Code size, in dwords
* \returns Code size, in dwords
*/
uint32_t dwords() const {
return m_code.size();
}
/**
* \brief Code size, in bytes
* \returns Code size, in bytes
*/
size_t size() const {
return m_code.size() * sizeof(uint32_t);
}
/**
* \brief Begin instruction iterator
*
* Points to the first instruction in the instruction
* block. The header, if any, will be skipped over.
* \returns Instruction iterator
*/
SpirvInstructionIterator begin() {
return SpirvInstructionIterator(
m_code.data(), 0, m_code.size());
}
/**
* \brief End instruction iterator
*
* Points to the end of the instruction block.
* \returns Instruction iterator
*/
SpirvInstructionIterator end() {
return SpirvInstructionIterator(nullptr, 0, 0);
}
/**
* \brief Allocates a new ID
*
* Returns a new valid ID and increments the
* maximum ID count stored in the header.
* \returns The new SPIR-V ID
*/
uint32_t allocId();
/**
* \brief Merges two code buffers
*
* This is useful to generate declarations or
* the SPIR-V header at the same time as the
* code when doing so in advance is impossible.
* \param [in] other Code buffer to append
*/
void append(const SpirvCodeBuffer& other);
/**
* \brief Appends an 32-bit word to the buffer
* \param [in] word The word to append
*/
void putWord(uint32_t word);
/**
* \brief Appends an instruction word to the buffer
*
* Adds a single word containing both the word count
* and the op code number for a single instruction.
* \param [in] opCode Operand code
* \param [in] wordCount Number of words
*/
void putIns(spv::Op opCode, uint16_t wordCount);
/**
* \brief Appends a 32-bit integer to the buffer
* \param [in] value The number to add
*/
void putInt32(uint32_t word);
/**
* \brief Appends a 64-bit integer to the buffer
*
* A 64-bit integer will take up two 32-bit words.
* \param [in] value 64-bit value to add
*/
void putInt64(uint64_t value);
/**
* \brief Appends a 32-bit float to the buffer
* \param [in] value The number to add
*/
void putFloat32(float value);
/**
* \brief Appends a 64-bit float to the buffer
* \param [in] value The number to add
*/
void putFloat64(double value);
/**
* \brief Appends a literal string to the buffer
* \param [in] str String to append to the buffer
*/
void putStr(const char* str);
/**
* \brief Adds the header to the buffer
*
* \param [in] version SPIR-V version
* \param [in] boundIds Number of bound IDs
*/
void putHeader(uint32_t version, uint32_t boundIds);
/**
* \brief Erases given number of dwords
*
* Removes data from the code buffer, starting
* at the current insertion offset.
* \param [in] size Number of words to remove
*/
void erase(size_t size);
/**
* \brief Computes length of a literal string
*
* \param [in] str The string to check
* \returns Number of words consumed by a string
*/
uint32_t strLen(const char* str);
/**
* \brief Stores the SPIR-V module to a stream
*
* The ability to save modules to a file
* exists mostly for debugging purposes.
* \param [in] stream Output stream
*/
void store(std::ostream& stream) const;
/**
* \brief Retrieves current insertion pointer
*
* Sometimes it may be necessay to insert code into the
* middle of the stream rather than appending it. This
* retrieves the current function pointer. Note that the
* pointer will become invalid if any code is inserted
* before the current pointer location.
* \returns Current instruction pointr
*/
size_t getInsertionPtr() const {
return m_ptr;
}
/**
* \brief Sets insertion pointer to a specific value
*
* Sets the insertion pointer to a value that was
* previously retrieved by \ref getInsertionPtr.
* \returns Current instruction pointr
*/
void beginInsertion(size_t ptr) {
m_ptr = ptr;
}
/**
* \brief Sets insertion pointer to the end
*
* After this call, new instructions will be
* appended to the stream. In other words,
* this will restore default behaviour.
* \returns Previous instruction pointer
*/
size_t endInsertion() {
return std::exchange(m_ptr, m_code.size());
}
private:
std::vector<uint32_t> m_code;
size_t m_ptr = 0;
};
}

View file

@ -0,0 +1,123 @@
#include "spirv_compression.h"
namespace dxvk {
SpirvCompressedBuffer::SpirvCompressedBuffer()
: m_size(0) {
}
SpirvCompressedBuffer::SpirvCompressedBuffer(SpirvCodeBuffer& code)
: m_size(code.dwords()) {
// The compression (detailed below) achieves roughly 55% of the
// original size on average and is very consistent, so an initial
// estimate of roughly 58% will be accurate most of the time.
const uint32_t* data = code.data();
m_code.reserve((m_size * 75) / 128);
std::array<uint32_t, 16> block;
uint32_t blockMask = 0;
uint32_t blockOffset = 0;
// The algorithm used is a simple variable-to-fixed compression that
// encodes up to two consecutive SPIR-V tokens into one DWORD using
// a small number of different encodings. While not achieving great
// compression ratios, the main goal is to allow decompression code
// to be fast, with short dependency chains.
// Compressed tokens are stored in blocks of 16 DWORDs, each preceeded
// by a single DWORD which stores the layout for each DWORD, two bits
// each. The supported layouts, are as follows:
// 0x0: 1x 32-bit; 0x1: 1x 20-bit + 1x 12-bit
// 0x2: 2x 16-bit; 0x3: 1x 12-bit + 1x 20-bit
// These layouts are chosen to allow reasonably efficient encoding of
// opcode tokens, which usually fit into 20 bits, followed by type IDs,
// which tend to be low as well since most types are defined early.
for (size_t i = 0; i < m_size; ) {
if (likely(i + 1 < m_size)) {
uint32_t a = data[i];
uint32_t b = data[i + 1];
uint32_t schema;
uint32_t encode;
if (std::max(a, b) < (1u << 16)) {
schema = 0x2;
encode = a | (b << 16);
} else if (a < (1u << 20) && b < (1u << 12)) {
schema = 0x1;
encode = a | (b << 20);
} else if (a < (1u << 12) && b < (1u << 20)) {
schema = 0x3;
encode = a | (b << 12);
} else {
schema = 0x0;
encode = a;
}
block[blockOffset] = encode;
blockMask |= schema << (blockOffset << 1);
blockOffset += 1;
i += schema ? 2 : 1;
} else {
block[blockOffset] = data[i++];
blockOffset += 1;
}
if (unlikely(blockOffset == 16) || unlikely(i == m_size)) {
m_code.insert(m_code.end(), blockMask);
m_code.insert(m_code.end(), block.begin(), block.begin() + blockOffset);
blockMask = 0;
blockOffset = 0;
}
}
// Only shrink the array if we have lots of overhead for some reason.
// This should only happen on shaders where our initial estimate was
// too small. In general, we want to avoid reallocation here.
if (m_code.capacity() > (m_code.size() * 10) / 9)
m_code.shrink_to_fit();
}
SpirvCompressedBuffer::~SpirvCompressedBuffer() {
}
SpirvCodeBuffer SpirvCompressedBuffer::decompress() const {
SpirvCodeBuffer code(m_size);
uint32_t* data = code.data();
uint32_t srcOffset = 0;
uint32_t dstOffset = 0;
constexpr uint32_t shiftAmounts = 0x0c101420;
while (dstOffset < m_size) {
uint32_t blockMask = m_code[srcOffset];
for (uint32_t i = 0; i < 16 && dstOffset < m_size; i++) {
// Use 64-bit integers for some of the operands so we can
// shift by 32 bits and not handle it as a special cases
uint32_t schema = (blockMask >> (i << 1)) & 0x3;
uint32_t shift = (shiftAmounts >> (schema << 3)) & 0xff;
uint64_t mask = ~(~0ull << shift);
uint64_t encode = m_code[srcOffset + i + 1];
data[dstOffset] = encode & mask;
if (likely(schema))
data[dstOffset + 1] = encode >> shift;
dstOffset += schema ? 2 : 1;
}
srcOffset += 17;
}
return code;
}
}

View file

@ -0,0 +1,38 @@
#pragma once
#include <vector>
#include "spirv_code_buffer.h"
namespace dxvk {
/**
* \brief Compressed SPIR-V code buffer
*
* Implements a fast in-memory compression
* to keep memory footprint low.
*/
class SpirvCompressedBuffer {
public:
SpirvCompressedBuffer();
SpirvCompressedBuffer(SpirvCodeBuffer& code);
~SpirvCompressedBuffer();
SpirvCodeBuffer decompress() const;
private:
size_t m_size;
std::vector<uint32_t> m_code;
void encodeDword(uint32_t dw);
uint32_t decodeDword(size_t& offset) const;
};
}

15
src/spirv/spirv_include.h Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include <spirv/unified1/spirv.hpp>
#include <spirv/unified1/GLSL.std.450.h>
#include "../util/log/log.h"
#include "../util/log/log_debug.h"
#include "../util/util_error.h"
#include "../util/util_flags.h"
#include "../util/util_likely.h"
#include "../util/util_string.h"
#include "../util/rc/util_rc.h"
#include "../util/rc/util_rc_ptr.h"

View file

@ -0,0 +1,158 @@
#pragma once
#include "spirv_include.h"
namespace dxvk {
/**
* \brief SPIR-V instruction
*
* Helps parsing a single instruction, providing
* access to the op code, instruction length and
* instruction arguments.
*/
class SpirvInstruction {
public:
SpirvInstruction() { }
SpirvInstruction(uint32_t* code, uint32_t offset, uint32_t length)
: m_code(code), m_offset(offset), m_length(length) { }
/**
* \brief SPIR-V Op code
* \returns The op code
*/
spv::Op opCode() const {
return static_cast<spv::Op>(
this->arg(0) & spv::OpCodeMask);
}
/**
* \brief Instruction length
* \returns Number of DWORDs
*/
uint32_t length() const {
return this->arg(0) >> spv::WordCountShift;
}
/**
* \brief Instruction offset
* \returns Offset in DWORDs
*/
uint32_t offset() const {
return m_offset;
}
/**
* \brief Argument value
*
* Retrieves an argument DWORD. Note that some instructions
* take 64-bit arguments which require more than one DWORD.
* Arguments start at index 1. Calling this method with an
* argument ID of 0 will return the opcode token.
* \param [in] idx Argument index, starting at 1
* \returns The argument value
*/
uint32_t arg(uint32_t idx) const {
const uint32_t index = m_offset + idx;
return index < m_length ? m_code[index] : 0;
}
/**
* \brief Argument string
*
* Retrieves a pointer to a UTF-8-encoded string.
* \param [in] idx Argument index, starting at 1
* \returns Pointer to the literal string
*/
const char* chr(uint32_t idx) const {
const uint32_t index = m_offset + idx;
return index < m_length ? reinterpret_cast<const char*>(&m_code[index]) : nullptr;
}
/**
* \brief Changes the value of an argument
*
* \param [in] idx Argument index, starting at 1
* \param [in] word New argument word
*/
void setArg(uint32_t idx, uint32_t word) const {
if (m_offset + idx < m_length)
m_code[m_offset + idx] = word;
}
private:
uint32_t* m_code = nullptr;
uint32_t m_offset = 0;
uint32_t m_length = 0;
};
/**
* \brief SPIR-V instruction iterator
*
* Convenient iterator that can be used
* to process raw SPIR-V shader code.
*/
class SpirvInstructionIterator {
public:
SpirvInstructionIterator() { }
SpirvInstructionIterator(uint32_t* code, uint32_t offset, uint32_t length)
: m_code (length != 0 ? code : nullptr),
m_offset(length != 0 ? offset : 0),
m_length(length) {
if ((length >= 5) && (offset == 0) && (m_code[0] == spv::MagicNumber))
this->advance(5);
}
SpirvInstructionIterator& operator ++ () {
this->advance(SpirvInstruction(m_code, m_offset, m_length).length());
return *this;
}
SpirvInstructionIterator operator ++ (int) {
SpirvInstructionIterator result = *this;
this->advance(SpirvInstruction(m_code, m_offset, m_length).length());
return result;
}
SpirvInstruction operator * () const {
return SpirvInstruction(m_code, m_offset, m_length);
}
bool operator == (const SpirvInstructionIterator& other) const {
return this->m_code == other.m_code
&& this->m_offset == other.m_offset
&& this->m_length == other.m_length;
}
bool operator != (const SpirvInstructionIterator& other) const {
return this->m_code != other.m_code
|| this->m_offset != other.m_offset
|| this->m_length != other.m_length;
}
private:
uint32_t* m_code = nullptr;
uint32_t m_offset = 0;
uint32_t m_length = 0;
void advance(uint32_t n) {
if (m_offset + n < m_length) {
m_offset += n;
} else {
m_code = nullptr;
m_offset = 0;
m_length = 0;
}
}
};
}

3877
src/spirv/spirv_module.cpp Normal file

File diff suppressed because it is too large Load diff

1320
src/spirv/spirv_module.h Normal file

File diff suppressed because it is too large Load diff

33
src/util/CMakeLists.txt Normal file
View file

@ -0,0 +1,33 @@
add_library(dxbc-util STATIC)
target_sources(dxbc-util PRIVATE
com/com_guid.cpp
com/com_guid.h
com/com_include.h
com/com_object.h
com/com_pointer.h
com/com_private_data.cpp
com/com_private_data.h
log/log_debug.cpp
log/log_debug.h
log/log.cpp
log/log.h
rc/util_rc_ptr.h
rc/util_rc.h
thread.cpp
thread.h
util_bit.h
util_enum.h
util_env.cpp
util_env.h
util_error.h
util_flags.h
util_likely.h
util_math.h
util_string.cpp
util_string.h
)
target_include_directories(dxbc-util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(dxbc-util PUBLIC windows-headers)

64
src/util/com/com_guid.cpp Normal file
View file

@ -0,0 +1,64 @@
#include <mutex>
#include <unordered_set>
#include "com_guid.h"
#include "../log/log.h"
#include "../../dxvk/dxvk_hash.h"
#include "../thread.h"
namespace dxvk {
struct GuidPair {
GuidPair() { };
GuidPair(IID a_, IID b_)
: a(a_), b(b_) { }
IID a, b;
size_t hash() const {
return size_t(a.Data1) ^ size_t(b.Data1);
}
bool eq(const GuidPair& other) const {
return a == other.a && b == other.b;
}
};
dxvk::mutex g_loggedQueryInterfaceErrorMutex;
std::unordered_set<GuidPair, DxvkHash, DxvkEq> g_loggedQueryInterfaceErrors;
bool logQueryInterfaceError(REFIID objectGuid, REFIID requestedGuid) {
if (Logger::logLevel() > LogLevel::Warn)
return false;
std::lock_guard lock(g_loggedQueryInterfaceErrorMutex);
return g_loggedQueryInterfaceErrors.emplace(objectGuid, requestedGuid).second;
}
}
std::ostream& operator << (std::ostream& os, REFIID guid) {
os << std::hex << std::setfill('0')
<< std::setw(8) << guid.Data1 << '-';
os << std::hex << std::setfill('0')
<< std::setw(4) << guid.Data2 << '-';
os << std::hex << std::setfill('0')
<< std::setw(4) << guid.Data3 << '-';
os << std::hex << std::setfill('0')
<< std::setw(2) << static_cast<short>(guid.Data4[0])
<< std::setw(2) << static_cast<short>(guid.Data4[1])
<< '-'
<< std::setw(2) << static_cast<short>(guid.Data4[2])
<< std::setw(2) << static_cast<short>(guid.Data4[3])
<< std::setw(2) << static_cast<short>(guid.Data4[4])
<< std::setw(2) << static_cast<short>(guid.Data4[5])
<< std::setw(2) << static_cast<short>(guid.Data4[6])
<< std::setw(2) << static_cast<short>(guid.Data4[7]);
return os;
}

21
src/util/com/com_guid.h Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <ostream>
#include <iomanip>
#include "com_include.h"
namespace dxvk {
/**
* \brief Checks whether an unknown GUID should be logged
*
* \param [in] objectGuid GUID of the object that QueryInterface is called on
* \param [in] requestGuid Requested unsupported GUID
* \returns \c true if the error should be logged
*/
bool logQueryInterfaceError(REFIID objectGuid, REFIID requestedGuid);
};
std::ostream& operator << (std::ostream& os, REFIID guid);

View file

@ -0,0 +1,17 @@
#pragma once
// GCC complains about the COM interfaces
// not having virtual destructors
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#endif // __GNUC__
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <unknwn.h>
// GCC: -std options disable certain keywords
// https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html
#if defined(__WINE__) && !defined(typeof)
#define typeof __typeof
#endif

123
src/util/com/com_object.h Normal file
View file

@ -0,0 +1,123 @@
#pragma once
#include <atomic>
#include "com_include.h"
#include "../util_likely.h"
namespace dxvk {
template<typename T>
class NoWrapper : public T {
public:
virtual ~NoWrapper() { }
};
/**
* \brief Reference-counted COM object
*
* This can serve as a templated base class for most
* COM objects. It implements AddRef and Release from
* \c IUnknown, and provides methods to increment and
* decrement private references which are not visible
* to the application.
*
* Having two reference counters is sadly necessary
* in order to not break games which steal internal
* references if the refefence count of an object is
+ greater than they expect. DXVK sometimes requires
* holding on to objects which the application wants
* to delete.
*/
template<typename Base>
class ComObject : public Base {
public:
virtual ~ComObject() { }
ULONG STDMETHODCALLTYPE AddRef() {
uint32_t refCount = m_refCount++;
if (unlikely(!refCount))
AddRefPrivate();
return refCount + 1;
}
ULONG STDMETHODCALLTYPE Release() {
uint32_t refCount = --m_refCount;
if (unlikely(!refCount))
ReleasePrivate();
return refCount;
}
void AddRefPrivate() {
++m_refPrivate;
}
void ReleasePrivate() {
uint32_t refPrivate = --m_refPrivate;
if (unlikely(!refPrivate)) {
m_refPrivate += 0x80000000;
delete this;
}
}
ULONG GetPrivateRefCount() const {
return m_refPrivate.load();
}
bool HasLiveReferences() const {
return bool(m_refCount.load() | (m_refPrivate.load() & 0x7FFFFFFF));
}
protected:
std::atomic<uint32_t> m_refCount = { 0ul };
std::atomic<uint32_t> m_refPrivate = { 0ul };
};
/**
* \brief Clamped, reference-counted COM object
*
* This version of ComObject ensures that the reference
* count does not wrap around if a release happens at zero.
* eg. [m_refCount = 0]
* Release()
* [m_refCount = 0]
* This is a notable quirk of D3D9's COM implementation
* and is relied upon by some games.
*/
template<typename Base>
class ComObjectClamp : public ComObject<Base> {
public:
ULONG STDMETHODCALLTYPE Release() {
ULONG refCount = this->m_refCount;
if (likely(refCount != 0ul)) {
this->m_refCount--;
refCount--;
if (refCount == 0ul)
this->ReleasePrivate();
}
return refCount;
}
};
template<typename T>
inline void InitReturnPtr(T** ptr) {
if (ptr != nullptr)
*ptr = nullptr;
}
}

146
src/util/com/com_pointer.h Normal file
View file

@ -0,0 +1,146 @@
#pragma once
#include "com_include.h"
namespace dxvk {
/**
* \brief Increment public ref count
*
* If the pointer is not \c nullptr, this
* calls \c AddRef for the given object.
* \returns Pointer to the object
*/
template<typename T>
T* ref(T* object) {
if (object != nullptr)
object->AddRef();
return object;
}
/**
* \brief Ref count methods for public references
*/
template<typename T, bool Public>
struct ComRef_ {
static void incRef(T* ptr) { ptr->AddRef(); }
static void decRef(T* ptr) { ptr->Release(); }
};
/**
* \brief Ref count methods for private references
*/
template<typename T>
struct ComRef_<T, false> {
static void incRef(T* ptr) { ptr->AddRefPrivate(); }
static void decRef(T* ptr) { ptr->ReleasePrivate(); }
};
/**
* \brief COM pointer
*
* Implements automatic reference
* counting for COM objects.
*/
template<typename T, bool Public = true>
class Com {
using ComRef = ComRef_<T, Public>;
public:
Com() { }
Com(std::nullptr_t) { }
Com(T* object)
: m_ptr(object) {
this->incRef();
}
Com(const Com& other)
: m_ptr(other.m_ptr) {
this->incRef();
}
Com(Com&& other)
: m_ptr(other.m_ptr) {
other.m_ptr = nullptr;
}
Com& operator = (T* object) {
this->decRef();
m_ptr = object;
this->incRef();
return *this;
}
Com& operator = (const Com& other) {
other.incRef();
this->decRef();
m_ptr = other.m_ptr;
return *this;
}
Com& operator = (Com&& other) {
this->decRef();
this->m_ptr = other.m_ptr;
other.m_ptr = nullptr;
return *this;
}
Com& operator = (std::nullptr_t) {
this->decRef();
m_ptr = nullptr;
return *this;
}
~Com() {
this->decRef();
}
T* operator -> () const {
return m_ptr;
}
T** operator & () { return &m_ptr; }
T* const* operator & () const { return &m_ptr; }
template<bool Public_>
bool operator == (const Com<T, Public_>& other) const { return m_ptr == other.m_ptr; }
template<bool Public_>
bool operator != (const Com<T, Public_>& other) const { return m_ptr != other.m_ptr; }
bool operator == (const T* other) const { return m_ptr == other; }
bool operator != (const T* other) const { return m_ptr != other; }
bool operator == (std::nullptr_t) const { return m_ptr == nullptr; }
bool operator != (std::nullptr_t) const { return m_ptr != nullptr; }
T* ref() const {
return dxvk::ref(m_ptr);
}
T* ptr() const {
return m_ptr;
}
Com<T, true> pubRef() const { return m_ptr; }
Com<T, false> prvRef() const { return m_ptr; }
private:
T* m_ptr = nullptr;
void incRef() const {
if (m_ptr != nullptr)
ComRef::incRef(m_ptr);
}
void decRef() const {
if (m_ptr != nullptr)
ComRef::decRef(m_ptr);
}
};
}

View file

@ -0,0 +1,171 @@
#include <cmath>
#include <cstring>
#include <cstdlib>
#include "com_private_data.h"
namespace dxvk {
ComPrivateDataEntry::ComPrivateDataEntry() { }
ComPrivateDataEntry::ComPrivateDataEntry(
REFGUID guid,
UINT size,
const void* data)
: m_guid(guid),
m_type(ComPrivateDataType::Data),
m_size(size),
m_data(std::malloc(size)) {
std::memcpy(m_data, data, size);
}
ComPrivateDataEntry::ComPrivateDataEntry(
REFGUID guid,
const IUnknown* iface)
: m_guid (guid),
m_type(ComPrivateDataType::Iface),
m_iface (const_cast<IUnknown*>(iface)) {
if (m_iface)
m_iface->AddRef();
}
ComPrivateDataEntry::~ComPrivateDataEntry() {
this->destroy();
}
ComPrivateDataEntry::ComPrivateDataEntry(ComPrivateDataEntry&& other)
: m_guid (other.m_guid),
m_type (other.m_type),
m_size (other.m_size),
m_data (other.m_data),
m_iface (other.m_iface) {
other.m_guid = __uuidof(IUnknown);
other.m_type = ComPrivateDataType::None;
other.m_size = 0;
other.m_data = nullptr;
other.m_iface = nullptr;
}
ComPrivateDataEntry& ComPrivateDataEntry::operator = (ComPrivateDataEntry&& other) {
this->destroy();
this->m_guid = other.m_guid;
this->m_type = other.m_type;
this->m_size = other.m_size;
this->m_data = other.m_data;
this->m_iface = other.m_iface;
other.m_guid = __uuidof(IUnknown);
other.m_type = ComPrivateDataType::None;
other.m_size = 0;
other.m_data = nullptr;
other.m_iface = nullptr;
return *this;
}
HRESULT ComPrivateDataEntry::get(UINT& size, void* data) const {
UINT minSize = 0;
if (m_type == ComPrivateDataType::Iface) minSize = sizeof(IUnknown*);
if (m_type == ComPrivateDataType::Data) minSize = m_size;
if (!data) {
size = minSize;
return S_OK;
}
HRESULT result = size < minSize
? DXGI_ERROR_MORE_DATA
: S_OK;
if (size >= minSize) {
if (m_type == ComPrivateDataType::Iface) {
if (m_iface)
m_iface->AddRef();
std::memcpy(data, &m_iface, minSize);
} else {
std::memcpy(data, m_data, minSize);
}
}
size = minSize;
return result;
}
void ComPrivateDataEntry::destroy() {
if (m_data)
std::free(m_data);
if (m_iface)
m_iface->Release();
}
HRESULT ComPrivateData::setData(
REFGUID guid,
UINT size,
const void* data) {
if (!data) {
for (auto it = m_entries.begin(); it != m_entries.end(); ++it) {
if (it->hasGuid(guid)) {
m_entries.erase(it);
return S_OK;
}
}
return S_FALSE;
}
this->insertEntry(ComPrivateDataEntry(guid, size, data));
return S_OK;
}
HRESULT ComPrivateData::setInterface(
REFGUID guid,
const IUnknown* iface) {
this->insertEntry(ComPrivateDataEntry(guid, iface));
return S_OK;
}
HRESULT ComPrivateData::getData(
REFGUID guid,
UINT* size,
void* data) {
if (!size)
return E_INVALIDARG;
auto entry = this->findEntry(guid);
if (!entry) {
*size = 0;
return DXGI_ERROR_NOT_FOUND;
}
return entry->get(*size, data);
}
ComPrivateDataEntry* ComPrivateData::findEntry(REFGUID guid) {
for (ComPrivateDataEntry& e : m_entries) {
if (e.hasGuid(guid))
return &e;
}
return nullptr;
}
void ComPrivateData::insertEntry(ComPrivateDataEntry&& entry) {
ComPrivateDataEntry srcEntry = std::move(entry);
ComPrivateDataEntry* dstEntry = this->findEntry(srcEntry.guid());
if (dstEntry)
*dstEntry = std::move(srcEntry);
else
m_entries.push_back(std::move(srcEntry));
}
}

View file

@ -0,0 +1,115 @@
#pragma once
#include <vector>
#include "com_include.h"
namespace dxvk {
/**
* \brief COM private data entry type
*/
enum ComPrivateDataType {
None,
Data,
Iface,
};
/**
* \brief Data entry for private storage
* Stores a single private storage item.
*/
class ComPrivateDataEntry {
public:
ComPrivateDataEntry();
ComPrivateDataEntry(
REFGUID guid,
UINT size,
const void* data);
ComPrivateDataEntry(
REFGUID guid,
const IUnknown* iface);
~ComPrivateDataEntry();
ComPrivateDataEntry (ComPrivateDataEntry&& other);
ComPrivateDataEntry& operator = (ComPrivateDataEntry&& other);
/**
* \brief The entry's GUID
* \returns The GUID
*/
REFGUID guid() const {
return m_guid;
}
/**
* \brief Checks whether the GUID matches another one
*
* GUIDs are used to identify private data entries.
* \param [in] guid The GUID to compare to
* \returns \c true if this entry holds the same GUID
*/
bool hasGuid(REFGUID guid) const {
return m_guid == guid;
}
/**
* \brief Retrieves stored data
*
* \param [in,out] size Destination buffer size
* \param [in] data Appliaction-provided buffer
* \returns \c S_OK on success, or \c DXGI_ERROR_MORE_DATA
* if the destination buffer is too small
*/
HRESULT get(UINT& size, void* data) const;
private:
GUID m_guid = __uuidof(IUnknown);
ComPrivateDataType m_type = ComPrivateDataType::None;
UINT m_size = 0;
void* m_data = nullptr;
IUnknown* m_iface = nullptr;
void destroy();
};
/**
* \brief Private storage for DXGI objects
*
* Provides storage for application-defined
* byte arrays or COM interfaces that can be
* retrieved using GUIDs.
*/
class ComPrivateData {
public:
HRESULT setData(
REFGUID guid,
UINT size,
const void* data);
HRESULT setInterface(
REFGUID guid,
const IUnknown* iface);
HRESULT getData(
REFGUID guid,
UINT* size,
void* data);
private:
std::vector<ComPrivateDataEntry> m_entries;
ComPrivateDataEntry* findEntry(REFGUID guid);
void insertEntry(ComPrivateDataEntry&& entry);
};
}

132
src/util/log/log.cpp Normal file
View file

@ -0,0 +1,132 @@
#include <utility>
#include "log.h"
#include "../util_env.h"
namespace dxvk {
Logger::Logger(const std::string& fileName)
: m_minLevel(getMinLogLevel()), m_fileName(fileName) {
}
Logger::~Logger() { }
void Logger::trace(const std::string& message) {
s_instance.emitMsg(LogLevel::Trace, message);
}
void Logger::debug(const std::string& message) {
s_instance.emitMsg(LogLevel::Debug, message);
}
void Logger::info(const std::string& message) {
s_instance.emitMsg(LogLevel::Info, message);
}
void Logger::warn(const std::string& message) {
s_instance.emitMsg(LogLevel::Warn, message);
}
void Logger::err(const std::string& message) {
s_instance.emitMsg(LogLevel::Error, message);
}
void Logger::log(LogLevel level, const std::string& message) {
s_instance.emitMsg(level, message);
}
void Logger::emitMsg(LogLevel level, const std::string& message) {
if (level >= m_minLevel) {
std::lock_guard<dxvk::mutex> lock(m_mutex);
static std::array<const char*, 5> s_prefixes
= {{ "trace: ", "debug: ", "info: ", "warn: ", "err: " }};
const char* prefix = s_prefixes.at(static_cast<uint32_t>(level));
if (!std::exchange(m_initialized, true)) {
#ifdef _WIN32
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
if (ntdll)
m_wineLogOutput = reinterpret_cast<PFN_wineLogOutput>(GetProcAddress(ntdll, "__wine_dbg_output"));
#endif
auto path = getFileName(m_fileName);
if (!path.empty())
m_fileStream = std::ofstream(str::topath(path.c_str()).c_str());
}
std::stringstream stream(message);
std::string line;
while (std::getline(stream, line, '\n')) {
std::stringstream outstream;
outstream << prefix << line << std::endl;
std::string adjusted = outstream.str();
if (!adjusted.empty()) {
if (m_wineLogOutput)
m_wineLogOutput(adjusted.c_str());
else
std::cerr << adjusted;
}
if (m_fileStream)
m_fileStream << adjusted;
}
}
}
std::string Logger::getFileName(const std::string& base) {
std::string path = env::getEnvVar("DXVK_LOG_PATH");
if (path == "none")
return std::string();
// Don't create a log file if we're writing to wine's console output
if (path.empty() && m_wineLogOutput)
return std::string();
if (!path.empty() && *path.rbegin() != '/')
path += '/';
std::string exeName = env::getExeBaseName();
path += exeName + "_" + base;
return path;
}
LogLevel Logger::getMinLogLevel() {
const std::array<std::pair<const char*, LogLevel>, 6> logLevels = {{
{ "trace", LogLevel::Trace },
{ "debug", LogLevel::Debug },
{ "info", LogLevel::Info },
{ "warn", LogLevel::Warn },
{ "error", LogLevel::Error },
{ "none", LogLevel::None },
}};
const std::string logLevelStr = env::getEnvVar("DXVK_LOG_LEVEL");
for (const auto& pair : logLevels) {
if (logLevelStr == pair.first)
return pair.second;
}
return LogLevel::Info;
}
}

69
src/util/log/log.h Normal file
View file

@ -0,0 +1,69 @@
#pragma once
#include <array>
#include <fstream>
#include <iostream>
#include <string>
#include "../thread.h"
namespace dxvk {
enum class LogLevel : uint32_t {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
None = 5,
};
using PFN_wineLogOutput = int (STDMETHODCALLTYPE *)(const char *);
/**
* \brief Logger
*
* Logger for one DLL. Creates a text file and
* writes all log messages to that file.
*/
class Logger {
public:
Logger(const std::string& file_name);
~Logger();
static void trace(const std::string& message);
static void debug(const std::string& message);
static void info (const std::string& message);
static void warn (const std::string& message);
static void err (const std::string& message);
static void log (LogLevel level, const std::string& message);
static LogLevel logLevel() {
return s_instance.m_minLevel;
}
private:
static Logger s_instance;
const LogLevel m_minLevel;
const std::string m_fileName;
dxvk::mutex m_mutex;
std::ofstream m_fileStream;
bool m_initialized = false;
PFN_wineLogOutput m_wineLogOutput = nullptr;
void emitMsg(LogLevel level, const std::string& message);
std::string getFileName(
const std::string& base);
static LogLevel getMinLogLevel();
};
}

View file

@ -0,0 +1,11 @@
#include "log_debug.h"
namespace dxvk::debug {
std::string methodName(const std::string& prettyName) {
size_t end = prettyName.find("(");
size_t begin = prettyName.substr(0, end).rfind(" ") + 1;
return prettyName.substr(begin,end - begin);
}
}

49
src/util/log/log_debug.h Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include <sstream>
#include "log.h"
#ifdef _MSC_VER
#define METHOD_NAME __FUNCSIG__
#else
#define METHOD_NAME __PRETTY_FUNCTION__
#endif
#define TRACE_ENABLED
#ifdef TRACE_ENABLED
#define TRACE(...) \
do { dxvk::debug::trace(METHOD_NAME, ##__VA_ARGS__); } while (0)
#else
#define TRACE(...) \
do { } while (0)
#endif
namespace dxvk::debug {
std::string methodName(const std::string& prettyName);
inline void traceArgs(std::stringstream& stream) { }
template<typename Arg1>
void traceArgs(std::stringstream& stream, const Arg1& arg1) {
stream << arg1;
}
template<typename Arg1, typename Arg2, typename... Args>
void traceArgs(std::stringstream& stream, const Arg1& arg1, const Arg2& arg2, const Args&... args) {
stream << arg1 << ",";
traceArgs(stream, arg2, args...);
}
template<typename... Args>
void trace(const std::string& funcName, const Args&... args) {
std::stringstream stream;
stream << methodName(funcName) << "(";
traceArgs(stream, args...);
stream << ")";
Logger::trace(stream.str());
}
}

38
src/util/rc/util_rc.h Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include <atomic>
#include "../util_likely.h"
namespace dxvk {
/**
* \brief Reference-counted object
*/
class RcObject {
public:
/**
* \brief Increments reference count
* \returns New reference count
*/
force_inline uint32_t incRef() {
return ++m_refCount;
}
/**
* \brief Decrements reference count
* \returns New reference count
*/
force_inline uint32_t decRef() {
return --m_refCount;
}
private:
std::atomic<uint32_t> m_refCount = { 0u };
};
}

124
src/util/rc/util_rc_ptr.h Normal file
View file

@ -0,0 +1,124 @@
#pragma once
#include <functional>
#include <ostream>
namespace dxvk {
/**
* \brief Pointer for reference-counted objects
*
* This only requires the given type to implement \c incRef
* and \c decRef methods that adjust the reference count.
* \tparam T Object type
*/
template<typename T>
class Rc {
template<typename Tx>
friend class Rc;
public:
Rc() { }
Rc(std::nullptr_t) { }
Rc(T* object)
: m_object(object) {
this->incRef();
}
Rc(const Rc& other)
: m_object(other.m_object) {
this->incRef();
}
template<typename Tx>
Rc(const Rc<Tx>& other)
: m_object(other.m_object) {
this->incRef();
}
Rc(Rc&& other)
: m_object(other.m_object) {
other.m_object = nullptr;
}
template<typename Tx>
Rc(Rc<Tx>&& other)
: m_object(other.m_object) {
other.m_object = nullptr;
}
Rc& operator = (std::nullptr_t) {
this->decRef();
m_object = nullptr;
return *this;
}
Rc& operator = (const Rc& other) {
other.incRef();
this->decRef();
m_object = other.m_object;
return *this;
}
template<typename Tx>
Rc& operator = (const Rc<Tx>& other) {
other.incRef();
this->decRef();
m_object = other.m_object;
return *this;
}
Rc& operator = (Rc&& other) {
this->decRef();
this->m_object = other.m_object;
other.m_object = nullptr;
return *this;
}
template<typename Tx>
Rc& operator = (Rc<Tx>&& other) {
this->decRef();
this->m_object = other.m_object;
other.m_object = nullptr;
return *this;
}
~Rc() {
this->decRef();
}
T& operator * () const { return *m_object; }
T* operator -> () const { return m_object; }
T* ptr() const { return m_object; }
bool operator == (const Rc& other) const { return m_object == other.m_object; }
bool operator != (const Rc& other) const { return m_object != other.m_object; }
bool operator == (std::nullptr_t) const { return m_object == nullptr; }
bool operator != (std::nullptr_t) const { return m_object != nullptr; }
private:
T* m_object = nullptr;
force_inline void incRef() const {
if (m_object != nullptr)
m_object->incRef();
}
force_inline void decRef() const {
if (m_object != nullptr) {
if (m_object->decRef() == 0)
delete m_object;
}
}
};
}
template<typename T>
std::ostream& operator << (std::ostream& os, const dxvk::Rc<T>& rc) {
return os << rc.ptr();
}

114
src/util/thread.cpp Normal file
View file

@ -0,0 +1,114 @@
#include <atomic>
#include "thread.h"
#include "util_likely.h"
#ifdef _WIN32
namespace dxvk {
thread::thread(ThreadProc&& proc)
: m_data(new ThreadData(std::move(proc))) {
m_data->handle = ::CreateThread(nullptr, 0x100000,
thread::threadProc, m_data, STACK_SIZE_PARAM_IS_A_RESERVATION,
&m_data->id);
if (!m_data->handle) {
delete m_data;
throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again), "Failed to create thread");
}
}
thread::~thread() {
if (joinable())
std::terminate();
}
void thread::join() {
if (!joinable())
throw std::system_error(std::make_error_code(std::errc::invalid_argument), "Thread not joinable");
if (get_id() == this_thread::get_id())
throw std::system_error(std::make_error_code(std::errc::resource_deadlock_would_occur), "Cannot join current thread");
if(::WaitForSingleObjectEx(m_data->handle, INFINITE, FALSE) == WAIT_FAILED)
throw std::system_error(std::make_error_code(std::errc::invalid_argument), "Joining thread failed");
detach();
}
void thread::set_priority(ThreadPriority priority) {
int32_t value;
switch (priority) {
default:
case ThreadPriority::Normal: value = THREAD_PRIORITY_NORMAL; break;
case ThreadPriority::Lowest: value = THREAD_PRIORITY_LOWEST; break;
}
if (m_data)
::SetThreadPriority(m_data->handle, int32_t(value));
}
uint32_t thread::hardware_concurrency() {
SYSTEM_INFO info = { };
::GetSystemInfo(&info);
return info.dwNumberOfProcessors;
}
DWORD WINAPI thread::threadProc(void* arg) {
auto data = reinterpret_cast<ThreadData*>(arg);
DWORD exitCode = 0;
try {
data->proc();
} catch (...) {
exitCode = 1;
}
data->decRef();
return exitCode;
}
}
namespace dxvk::this_thread {
bool isInModuleDetachment() {
using PFN_RtlDllShutdownInProgress = BOOLEAN (WINAPI *)();
static auto RtlDllShutdownInProgress = reinterpret_cast<PFN_RtlDllShutdownInProgress>(
::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "RtlDllShutdownInProgress"));
return RtlDllShutdownInProgress();
}
}
#else
namespace dxvk::this_thread {
static std::atomic<uint32_t> g_threadCtr = { 0u };
static thread_local uint32_t g_threadId = 0u;
// This implementation returns thread ids unique to the current instance.
// ie. if you use this across multiple .so's then you might get conflicting ids.
//
// This isn't an issue for us, as it is only used by the spinlock implementation,
// but may be for you if you use this elsewhere.
uint32_t get_id() {
if (unlikely(!g_threadId))
g_threadId = ++g_threadCtr;
return g_threadId;
}
}
#endif

342
src/util/thread.h Normal file
View file

@ -0,0 +1,342 @@
#pragma once
#include <chrono>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
#include <utility>
#include "util_error.h"
#include "./com/com_include.h"
#include "./rc/util_rc.h"
#include "./rc/util_rc_ptr.h"
namespace dxvk {
/**
* \brief Thread priority
*/
enum class ThreadPriority : int32_t {
Normal,
Lowest,
};
#ifdef _WIN32
using ThreadProc = std::function<void()>;
/**
* \brief Thread object
*/
struct ThreadData {
ThreadData(ThreadProc&& proc_)
: proc(std::move(proc_)) { }
~ThreadData() {
if (handle)
CloseHandle(handle);
}
HANDLE handle = nullptr;
DWORD id = 0;
std::atomic<uint32_t> refs = { 2u };
ThreadProc proc;
void decRef() {
if (refs.fetch_sub(1, std::memory_order_release) == 1)
delete this;
}
};
/**
* \brief Thread wrapper
*
* Drop-in replacement for std::thread
* using plain win32 threads.
*/
class thread {
public:
using id = uint32_t;
using native_handle_type = HANDLE;
thread() { }
explicit thread(ThreadProc&& proc);
~thread();
thread(thread&& other)
: m_data(std::exchange(other.m_data, nullptr)) { }
thread& operator = (thread&& other) {
if (m_data)
m_data->decRef();
m_data = std::exchange(other.m_data, nullptr);
return *this;
}
void detach() {
m_data->decRef();
m_data = nullptr;
}
bool joinable() const {
return m_data != nullptr;
}
id get_id() const {
return joinable() ? m_data->id : id();
}
native_handle_type native_handle() const {
return joinable() ? m_data->handle : native_handle_type();
}
void swap(thread& other) {
std::swap(m_data, other.m_data);
}
void join();
void set_priority(ThreadPriority priority);
static uint32_t hardware_concurrency();
private:
ThreadData* m_data = nullptr;
static DWORD WINAPI threadProc(void* arg);
};
namespace this_thread {
inline void yield() {
SwitchToThread();
}
inline thread::id get_id() {
return thread::id(GetCurrentThreadId());
}
bool isInModuleDetachment();
}
/**
* \brief SRW-based mutex implementation
*
* Drop-in replacement for \c std::mutex that uses Win32
* SRW locks, which are implemented with \c futex in wine.
*/
class mutex {
public:
using native_handle_type = PSRWLOCK;
mutex() { }
mutex(const mutex&) = delete;
mutex& operator = (const mutex&) = delete;
void lock() {
AcquireSRWLockExclusive(&m_lock);
}
void unlock() {
ReleaseSRWLockExclusive(&m_lock);
}
bool try_lock() {
return TryAcquireSRWLockExclusive(&m_lock);
}
native_handle_type native_handle() {
return &m_lock;
}
private:
SRWLOCK m_lock = SRWLOCK_INIT;
};
/**
* \brief Recursive mutex implementation
*
* Drop-in replacement for \c std::recursive_mutex that
* uses Win32 critical sections.
*/
class recursive_mutex {
public:
using native_handle_type = PCRITICAL_SECTION;
recursive_mutex() {
InitializeCriticalSection(&m_lock);
}
~recursive_mutex() {
DeleteCriticalSection(&m_lock);
}
recursive_mutex(const recursive_mutex&) = delete;
recursive_mutex& operator = (const recursive_mutex&) = delete;
void lock() {
EnterCriticalSection(&m_lock);
}
void unlock() {
LeaveCriticalSection(&m_lock);
}
bool try_lock() {
return TryEnterCriticalSection(&m_lock);
}
native_handle_type native_handle() {
return &m_lock;
}
private:
CRITICAL_SECTION m_lock;
};
/**
* \brief SRW-based condition variable implementation
*
* Drop-in replacement for \c std::condition_variable that
* uses Win32 condition variables on SRW locks.
*/
class condition_variable {
public:
using native_handle_type = PCONDITION_VARIABLE;
condition_variable() {
InitializeConditionVariable(&m_cond);
}
condition_variable(condition_variable&) = delete;
condition_variable& operator = (condition_variable&) = delete;
void notify_one() {
WakeConditionVariable(&m_cond);
}
void notify_all() {
WakeAllConditionVariable(&m_cond);
}
void wait(std::unique_lock<dxvk::mutex>& lock) {
auto srw = lock.mutex()->native_handle();
SleepConditionVariableSRW(&m_cond, srw, INFINITE, 0);
}
template<typename Predicate>
void wait(std::unique_lock<dxvk::mutex>& lock, Predicate pred) {
while (!pred())
wait(lock);
}
template<typename Clock, typename Duration>
std::cv_status wait_until(std::unique_lock<dxvk::mutex>& lock, const std::chrono::time_point<Clock, Duration>& time) {
auto now = Clock::now();
return (now < time)
? wait_for(lock, now - time)
: std::cv_status::timeout;
}
template<typename Clock, typename Duration, typename Predicate>
bool wait_until(std::unique_lock<dxvk::mutex>& lock, const std::chrono::time_point<Clock, Duration>& time, Predicate pred) {
if (pred())
return true;
auto now = Clock::now();
return now < time && wait_for(lock, now - time, pred);
}
template<typename Rep, typename Period>
std::cv_status wait_for(std::unique_lock<dxvk::mutex>& lock, const std::chrono::duration<Rep, Period>& timeout) {
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
auto srw = lock.mutex()->native_handle();
return SleepConditionVariableSRW(&m_cond, srw, ms.count(), 0)
? std::cv_status::no_timeout
: std::cv_status::timeout;
}
template<typename Rep, typename Period, typename Predicate>
bool wait_for(std::unique_lock<dxvk::mutex>& lock, const std::chrono::duration<Rep, Period>& timeout, Predicate pred) {
bool result = pred();
if (!result && wait_for(lock, timeout) == std::cv_status::no_timeout)
result = pred();
return result;
}
native_handle_type native_handle() {
return &m_cond;
}
private:
CONDITION_VARIABLE m_cond;
};
#else
class thread : public std::thread {
public:
using std::thread::thread;
void set_priority(ThreadPriority priority) {
::sched_param param = {};
int32_t policy;
switch (priority) {
default:
case ThreadPriority::Normal: policy = SCHED_OTHER; break;
case ThreadPriority::Lowest: policy = SCHED_IDLE; break;
}
::pthread_setschedparam(this->native_handle(), policy, &param);
}
};
using mutex = std::mutex;
using recursive_mutex = std::recursive_mutex;
using condition_variable = std::condition_variable;
namespace this_thread {
inline void yield() {
std::this_thread::yield();
}
uint32_t get_id();
inline bool isInModuleDetachment() {
return false;
}
}
#endif
}

519
src/util/util_bit.h Normal file
View file

@ -0,0 +1,519 @@
#pragma once
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
#define DXVK_ARCH_X86
#if defined(__x86_64__) || defined(_M_X64)
#define DXVK_ARCH_X86_64
#endif
#elif defined(__aarch64__) || defined(_M_ARM64)
#define DXVK_ARCH_ARM64
#else
#error "Unknown CPU Architecture"
#endif
#ifdef DXVK_ARCH_X86
#ifndef _MSC_VER
#if defined(__WINE__) && defined(__clang__)
#pragma push_macro("_WIN32")
#undef _WIN32
#endif
#include <x86intrin.h>
#if defined(__WINE__) && defined(__clang__)
#pragma pop_macro("_WIN32")
#endif
#else
#include <intrin.h>
#endif
#endif
#include "util_likely.h"
#include "util_math.h"
#include <cstdint>
#include <cstring>
#include <iterator>
#include <type_traits>
#include <vector>
namespace dxvk::bit {
template<typename T, typename J>
T cast(const J& src) {
static_assert(sizeof(T) == sizeof(J));
static_assert(std::is_trivially_copyable<J>::value && std::is_trivial<T>::value);
T dst;
std::memcpy(&dst, &src, sizeof(T));
return dst;
}
template<typename T>
T extract(T value, uint32_t fst, uint32_t lst) {
return (value >> fst) & ~(~T(0) << (lst - fst + 1));
}
inline uint32_t popcntStep(uint32_t n, uint32_t mask, uint32_t shift) {
return (n & mask) + ((n & ~mask) >> shift);
}
inline uint32_t popcnt(uint32_t n) {
n = popcntStep(n, 0x55555555, 1);
n = popcntStep(n, 0x33333333, 2);
n = popcntStep(n, 0x0F0F0F0F, 4);
n = popcntStep(n, 0x00FF00FF, 8);
n = popcntStep(n, 0x0000FFFF, 16);
return n;
}
inline uint32_t tzcnt(uint32_t n) {
#if defined(_MSC_VER) && !defined(__clang__)
return _tzcnt_u32(n);
#elif defined(__BMI__)
return __tzcnt_u32(n);
#elif defined(DXVK_ARCH_X86) && (defined(__GNUC__) || defined(__clang__))
// tzcnt is encoded as rep bsf, so we can use it on all
// processors, but the behaviour of zero inputs differs:
// - bsf: zf = 1, cf = ?, result = ?
// - tzcnt: zf = 0, cf = 1, result = 32
// We'll have to handle this case manually.
uint32_t res;
uint32_t tmp;
asm (
"tzcnt %2, %0;"
"mov $32, %1;"
"test %2, %2;"
"cmovz %1, %0;"
: "=&r" (res), "=&r" (tmp)
: "r" (n)
: "cc");
return res;
#elif defined(__GNUC__) || defined(__clang__)
return n != 0 ? __builtin_ctz(n) : 32;
#else
uint32_t r = 31;
n &= -n;
r -= (n & 0x0000FFFF) ? 16 : 0;
r -= (n & 0x00FF00FF) ? 8 : 0;
r -= (n & 0x0F0F0F0F) ? 4 : 0;
r -= (n & 0x33333333) ? 2 : 0;
r -= (n & 0x55555555) ? 1 : 0;
return n != 0 ? r : 32;
#endif
}
inline uint32_t tzcnt(uint64_t n) {
#if defined(DXVK_ARCH_X86_64) && defined(_MSC_VER) && !defined(__clang__)
return (uint32_t)_tzcnt_u64(n);
#elif defined(DXVK_ARCH_X86_64) && defined(__BMI__)
return __tzcnt_u64(n);
#elif defined(DXVK_ARCH_X86_64) && (defined(__GNUC__) || defined(__clang__))
uint64_t res;
uint64_t tmp;
asm (
"tzcnt %2, %0;"
"mov $64, %1;"
"test %2, %2;"
"cmovz %1, %0;"
: "=&r" (res), "=&r" (tmp)
: "r" (n)
: "cc");
return res;
#elif defined(__GNUC__) || defined(__clang__)
return n != 0 ? __builtin_ctzll(n) : 64;
#else
uint32_t lo = uint32_t(n);
if (lo) {
return tzcnt(lo);
} else {
uint32_t hi = uint32_t(n >> 32);
return tzcnt(hi) + 32;
}
#endif
}
inline uint32_t lzcnt(uint32_t n) {
#if (defined(_MSC_VER) && !defined(__clang__)) || defined(__LZCNT__)
return _lzcnt_u32(n);
#elif defined(__GNUC__) || defined(__clang__)
return n != 0 ? __builtin_clz(n) : 32;
#else
uint32_t r = 0;
if (n == 0) return 32;
if (n <= 0x0000FFFF) { r += 16; n <<= 16; }
if (n <= 0x00FFFFFF) { r += 8; n <<= 8; }
if (n <= 0x0FFFFFFF) { r += 4; n <<= 4; }
if (n <= 0x3FFFFFFF) { r += 2; n <<= 2; }
if (n <= 0x7FFFFFFF) { r += 1; n <<= 1; }
return r;
#endif
}
template<typename T>
uint32_t pack(T& dst, uint32_t& shift, T src, uint32_t count) {
constexpr uint32_t Bits = 8 * sizeof(T);
if (likely(shift < Bits))
dst |= src << shift;
shift += count;
return shift > Bits ? shift - Bits : 0;
}
template<typename T>
uint32_t unpack(T& dst, T src, uint32_t& shift, uint32_t count) {
constexpr uint32_t Bits = 8 * sizeof(T);
if (likely(shift < Bits))
dst = (src >> shift) & ((T(1) << count) - 1);
shift += count;
return shift > Bits ? shift - Bits : 0;
}
/**
* \brief Compares two aligned structs bit by bit
*
* \param [in] a First struct
* \param [in] b Second struct
* \returns \c true if the structs are equal
*/
template<typename T>
bool bcmpeq(const T* a, const T* b) {
static_assert(alignof(T) >= 16);
#if defined(DXVK_ARCH_X86) && (defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER))
auto ai = reinterpret_cast<const __m128i*>(a);
auto bi = reinterpret_cast<const __m128i*>(b);
size_t i = 0;
#if defined(__clang__)
#pragma nounroll
#elif defined(__GNUC__)
#pragma GCC unroll 0
#endif
for ( ; i < 2 * (sizeof(T) / 32); i += 2) {
__m128i eq0 = _mm_cmpeq_epi8(
_mm_load_si128(ai + i),
_mm_load_si128(bi + i));
__m128i eq1 = _mm_cmpeq_epi8(
_mm_load_si128(ai + i + 1),
_mm_load_si128(bi + i + 1));
__m128i eq = _mm_and_si128(eq0, eq1);
int mask = _mm_movemask_epi8(eq);
if (mask != 0xFFFF)
return false;
}
for ( ; i < sizeof(T) / 16; i++) {
__m128i eq = _mm_cmpeq_epi8(
_mm_load_si128(ai + i),
_mm_load_si128(bi + i));
int mask = _mm_movemask_epi8(eq);
if (mask != 0xFFFF)
return false;
}
return true;
#else
return !std::memcmp(a, b, sizeof(T));
#endif
}
template <size_t Bits>
class bitset {
static constexpr size_t Dwords = align(Bits, 32) / 32;
public:
constexpr bitset()
: m_dwords() {
}
constexpr bool get(uint32_t idx) const {
uint32_t dword = 0;
uint32_t bit = idx;
// Compiler doesn't remove this otherwise.
if constexpr (Dwords > 1) {
dword = idx / 32;
bit = idx % 32;
}
return m_dwords[dword] & (1u << bit);
}
constexpr void set(uint32_t idx, bool value) {
uint32_t dword = 0;
uint32_t bit = idx;
// Compiler doesn't remove this otherwise.
if constexpr (Dwords > 1) {
dword = idx / 32;
bit = idx % 32;
}
if (value)
m_dwords[dword] |= 1u << bit;
else
m_dwords[dword] &= ~(1u << bit);
}
constexpr bool exchange(uint32_t idx, bool value) {
bool oldValue = get(idx);
set(idx, value);
return oldValue;
}
constexpr void flip(uint32_t idx) {
uint32_t dword = 0;
uint32_t bit = idx;
// Compiler doesn't remove this otherwise.
if constexpr (Dwords > 1) {
dword = idx / 32;
bit = idx % 32;
}
m_dwords[dword] ^= 1u << bit;
}
constexpr void setAll() {
if constexpr (Bits % 32 == 0) {
for (size_t i = 0; i < Dwords; i++)
m_dwords[i] = std::numeric_limits<uint32_t>::max();
}
else {
for (size_t i = 0; i < Dwords - 1; i++)
m_dwords[i] = std::numeric_limits<uint32_t>::max();
m_dwords[Dwords - 1] = (1u << (Bits % 32)) - 1;
}
}
constexpr void clearAll() {
for (size_t i = 0; i < Dwords; i++)
m_dwords[i] = 0;
}
constexpr bool any() const {
for (size_t i = 0; i < Dwords; i++) {
if (m_dwords[i] != 0)
return true;
}
return false;
}
constexpr uint32_t& dword(uint32_t idx) {
return m_dwords[idx];
}
constexpr size_t bitCount() {
return Bits;
}
constexpr size_t dwordCount() {
return Dwords;
}
constexpr bool operator [] (uint32_t idx) const {
return get(idx);
}
constexpr void setN(uint32_t bits) {
uint32_t fullDwords = bits / 32;
uint32_t offset = bits % 32;
for (size_t i = 0; i < fullDwords; i++)
m_dwords[i] = std::numeric_limits<uint32_t>::max();
if (offset > 0)
m_dwords[fullDwords] = (1u << offset) - 1;
}
private:
uint32_t m_dwords[Dwords];
};
class bitvector {
public:
bool get(uint32_t idx) const {
uint32_t dword = idx / 32;
uint32_t bit = idx % 32;
return m_dwords[dword] & (1u << bit);
}
void ensureSize(uint32_t bitCount) {
uint32_t dword = bitCount / 32;
if (unlikely(dword >= m_dwords.size())) {
m_dwords.resize(dword + 1);
}
m_bitCount = std::max(m_bitCount, bitCount);
}
void set(uint32_t idx, bool value) {
ensureSize(idx + 1);
uint32_t dword = 0;
uint32_t bit = idx;
if (value)
m_dwords[dword] |= 1u << bit;
else
m_dwords[dword] &= ~(1u << bit);
}
bool exchange(uint32_t idx, bool value) {
ensureSize(idx + 1);
bool oldValue = get(idx);
set(idx, value);
return oldValue;
}
void flip(uint32_t idx) {
ensureSize(idx + 1);
uint32_t dword = idx / 32;
uint32_t bit = idx % 32;
m_dwords[dword] ^= 1u << bit;
}
void setAll() {
if (m_bitCount % 32 == 0) {
for (size_t i = 0; i < m_dwords.size(); i++)
m_dwords[i] = std::numeric_limits<uint32_t>::max();
}
else {
for (size_t i = 0; i < m_dwords.size() - 1; i++)
m_dwords[i] = std::numeric_limits<uint32_t>::max();
m_dwords[m_dwords.size() - 1] = (1u << (m_bitCount % 32)) - 1;
}
}
void clearAll() {
for (size_t i = 0; i < m_dwords.size(); i++)
m_dwords[i] = 0;
}
bool any() const {
for (size_t i = 0; i < m_dwords.size(); i++) {
if (m_dwords[i] != 0)
return true;
}
return false;
}
uint32_t& dword(uint32_t idx) {
return m_dwords[idx];
}
size_t bitCount() const {
return m_bitCount;
}
size_t dwordCount() const {
return m_dwords.size();
}
bool operator [] (uint32_t idx) const {
return get(idx);
}
void setN(uint32_t bits) {
ensureSize(bits);
uint32_t fullDwords = bits / 32;
uint32_t offset = bits % 32;
for (size_t i = 0; i < fullDwords; i++)
m_dwords[i] = std::numeric_limits<uint32_t>::max();
if (offset > 0)
m_dwords[fullDwords] = (1u << offset) - 1;
}
private:
std::vector<uint32_t> m_dwords;
uint32_t m_bitCount = 0;
};
class BitMask {
public:
class iterator {
public:
using iterator_category = std::input_iterator_tag;
using value_type = uint32_t;
using difference_type = uint32_t;
using pointer = const uint32_t*;
using reference = uint32_t;
explicit iterator(uint32_t flags)
: m_mask(flags) { }
iterator& operator ++ () {
m_mask &= m_mask - 1;
return *this;
}
iterator operator ++ (int) {
iterator retval = *this;
m_mask &= m_mask - 1;
return retval;
}
uint32_t operator * () const {
#if (defined(__GNUC__) || defined(__clang__)) && !defined(__BMI__) && defined(DXVK_ARCH_X86)
uint32_t res;
asm ("tzcnt %1,%0"
: "=r" (res)
: "r" (m_mask)
: "cc");
return res;
#else
return tzcnt(m_mask);
#endif
}
bool operator == (iterator other) const { return m_mask == other.m_mask; }
bool operator != (iterator other) const { return m_mask != other.m_mask; }
private:
uint32_t m_mask;
};
BitMask()
: m_mask(0) { }
BitMask(uint32_t n)
: m_mask(n) { }
iterator begin() {
return iterator(m_mask);
}
iterator end() {
return iterator(0);
}
private:
uint32_t m_mask;
};
}

7
src/util/util_enum.h Normal file
View file

@ -0,0 +1,7 @@
#pragma once
#define ENUM_NAME(name) \
case name: return os << #name
#define ENUM_DEFAULT(name) \
default: return os << static_cast<int32_t>(e)

131
src/util/util_env.cpp Normal file
View file

@ -0,0 +1,131 @@
#include <array>
#include <cstdlib>
#include <filesystem>
#include <numeric>
#ifdef __linux__
#include <unistd.h>
#include <limits.h>
#endif
#include "util_env.h"
#include "./com/com_include.h"
namespace dxvk::env {
std::string getEnvVar(const char* name) {
#ifdef _WIN32
std::vector<WCHAR> result;
result.resize(MAX_PATH + 1);
DWORD len = ::GetEnvironmentVariableW(str::tows(name).c_str(), result.data(), MAX_PATH);
result.resize(len);
return str::fromws(result.data());
#else
const char* result = std::getenv(name);
return result ? result : "";
#endif
}
size_t matchFileExtension(const std::string& name, const char* ext) {
auto pos = name.find_last_of('.');
if (pos == std::string::npos)
return pos;
bool matches = std::accumulate(name.begin() + pos + 1, name.end(), true,
[&ext] (bool current, char a) {
if (a >= 'A' && a <= 'Z')
a += 'a' - 'A';
return current && *ext && a == *(ext++);
});
return matches ? pos : std::string::npos;
}
std::string getExeName() {
std::string fullPath = getExePath();
auto n = fullPath.find_last_of(env::PlatformDirSlash);
return (n != std::string::npos)
? fullPath.substr(n + 1)
: fullPath;
}
std::string getExeBaseName() {
auto exeName = getExeName();
#ifdef _WIN32
auto extp = matchFileExtension(exeName, "exe");
if (extp != std::string::npos)
exeName.erase(extp);
#endif
return exeName;
}
std::string getExePath() {
#if defined(_WIN32)
std::vector<WCHAR> exePath;
exePath.resize(MAX_PATH + 1);
DWORD len = ::GetModuleFileNameW(NULL, exePath.data(), MAX_PATH);
exePath.resize(len);
return str::fromws(exePath.data());
#elif defined(__linux__)
std::array<char, PATH_MAX> exePath = {};
size_t count = readlink("/proc/self/exe", exePath.data(), exePath.size());
return std::string(exePath.begin(), exePath.begin() + count);
#endif
}
void setThreadName(const std::string& name) {
#ifdef _WIN32
using SetThreadDescriptionProc = HRESULT (WINAPI *) (HANDLE, PCWSTR);
static auto SetThreadDescription = reinterpret_cast<SetThreadDescriptionProc>(
::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "SetThreadDescription"));
if (SetThreadDescription) {
std::array<wchar_t, 16> wideName = { };
str::transcodeString(
wideName.data(), wideName.size() - 1,
name.data(), name.size());
SetThreadDescription(::GetCurrentThread(), wideName.data());
}
#else
std::array<char, 16> posixName = {};
dxvk::str::strlcpy(posixName.data(), name.c_str(), 16);
::pthread_setname_np(pthread_self(), posixName.data());
#endif
}
bool createDirectory(const std::string& path) {
#ifdef _WIN32
std::array<WCHAR, MAX_PATH + 1> widePath;
size_t length = str::transcodeString(
widePath.data(), widePath.size() - 1,
path.data(), path.size());
widePath[length] = L'\0';
return !!CreateDirectoryW(widePath.data(), nullptr);
#else
return std::filesystem::create_directories(path);
#endif
}
}

79
src/util/util_env.h Normal file
View file

@ -0,0 +1,79 @@
#pragma once
#include "util_string.h"
namespace dxvk::env {
#ifdef _WIN32
constexpr char PlatformDirSlash = '\\';
#else
constexpr char PlatformDirSlash = '/';
#endif
/**
* \brief Checks whether the host platform is 32-bit
*/
constexpr bool is32BitHostPlatform() {
return sizeof(void*) == 4;
}
/**
* \brief Gets environment variable
*
* If the variable is not defined, this will return
* an empty string. Note that environment variables
* may be defined with an empty value.
* \param [in] name Name of the variable
* \returns Value of the variable
*/
std::string getEnvVar(const char* name);
/**
* \brief Checks whether a file name has a given extension
*
* \param [in] name File name
* \param [in] ext Extension to match, in lowercase letters
* \returns Position of the extension within the file name, or
* \c std::string::npos if the file has a different extension
*/
size_t matchFileExtension(const std::string& name, const char* ext);
/**
* \brief Gets the executable name
*
* Returns the base name (not the full path) of the
* program executable, including the file extension.
* This function should be used to identify programs.
* \returns Executable name
*/
std::string getExeName();
/**
* \brief Gets the executable name without extension
*
* Same as \ref getExeName but without the file extension.
* \returns Executable name
*/
std::string getExeBaseName();
/**
* \brief Gets full path to executable
* \returns Path to executable
*/
std::string getExePath();
/**
* \brief Sets name of the calling thread
* \param [in] name Thread name
*/
void setThreadName(const std::string& name);
/**
* \brief Creates a directory
*
* \param [in] path Path to directory
* \returns \c true on success
*/
bool createDirectory(const std::string& path);
}

31
src/util/util_error.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <string>
namespace dxvk {
/**
* \brief DXVK error
*
* A generic exception class that stores a
* message. Exceptions should be logged.
*/
class DxvkError {
public:
DxvkError() { }
DxvkError(std::string&& message)
: m_message(std::move(message)) { }
const std::string& message() const {
return m_message;
}
private:
std::string m_message;
};
}

110
src/util/util_flags.h Normal file
View file

@ -0,0 +1,110 @@
#pragma once
#include <type_traits>
#include "util_bit.h"
namespace dxvk {
template<typename T>
class Flags {
public:
using IntType = std::underlying_type_t<T>;
Flags() { }
Flags(IntType t)
: m_bits(t) { }
template<typename... Tx>
Flags(T f, Tx... fx) {
this->set(f, fx...);
}
template<typename... Tx>
void set(Tx... fx) {
m_bits |= bits(fx...);
}
void set(Flags flags) {
m_bits |= flags.m_bits;
}
template<typename... Tx>
void clr(Tx... fx) {
m_bits &= ~bits(fx...);
}
void clr(Flags flags) {
m_bits &= ~flags.m_bits;
}
template<typename... Tx>
bool any(Tx... fx) const {
return (m_bits & bits(fx...)) != 0;
}
template<typename... Tx>
bool all(Tx... fx) const {
const IntType mask = bits(fx...);
return (m_bits & mask) == mask;
}
bool test(T f) const {
return this->any(f);
}
bool isClear() const {
return m_bits == 0;
}
void clrAll() {
m_bits = 0;
}
IntType raw() const {
return m_bits;
}
Flags operator & (const Flags& other) const {
return Flags(m_bits & other.m_bits);
}
Flags operator | (const Flags& other) const {
return Flags(m_bits | other.m_bits);
}
Flags operator ^ (const Flags& other) const {
return Flags(m_bits ^ other.m_bits);
}
bool operator == (const Flags& other) const {
return m_bits == other.m_bits;
}
bool operator != (const Flags& other) const {
return m_bits != other.m_bits;
}
private:
IntType m_bits = 0;
static IntType bit(T f) {
return IntType(1) << static_cast<IntType>(f);
}
template<typename... Tx>
static IntType bits(T f, Tx... fx) {
return bit(f) | bits(fx...);
}
static IntType bits() {
return 0;
}
};
}

11
src/util/util_likely.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#ifdef __GNUC__
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
#define force_inline inline __attribute__((always_inline))
#else
#define likely(x) (x)
#define unlikely(x) (x)
#define force_inline inline
#endif

39
src/util/util_math.h Normal file
View file

@ -0,0 +1,39 @@
#pragma once
#include <cmath>
namespace dxvk {
constexpr size_t CACHE_LINE_SIZE = 64;
template<typename T>
constexpr T clamp(T n, T lo, T hi) {
if (n < lo) return lo;
if (n > hi) return hi;
return n;
}
template<typename T, typename U = T>
constexpr T align(T what, U to) {
return (what + to - 1) & ~(to - 1);
}
template<typename T, typename U = T>
constexpr T alignDown(T what, U to) {
return (what / to) * to;
}
// Equivalent of std::clamp for use with floating point numbers
// Handles (-){INFINITY,NAN} cases.
// Will return min in cases of NAN, etc.
inline float fclamp(float value, float min, float max) {
return std::fmin(
std::fmax(value, min), max);
}
template<typename T>
inline T divCeil(T dividend, T divisor) {
return (dividend + divisor - 1) / divisor;
}
}

234
src/util/util_string.cpp Normal file
View file

@ -0,0 +1,234 @@
#include "util_string.h"
namespace dxvk::str {
const uint8_t* decodeTypedChar(
const uint8_t* begin,
const uint8_t* end,
uint32_t& ch) {
uint32_t first = begin[0];
if (likely(first < 0x80)) {
// Basic ASCII character
ch = uint32_t(first);
return begin + 1;
} else if (unlikely(first < 0xC0)) {
// Character starts with a continuation byte,
// just skip until we find the next valid prefix
while ((begin < end) && (((*begin) & 0xC0) == 0x80))
begin += 1;
ch = uint32_t('?');
return begin;
} else {
// The number of leading 1 bits in the first byte
// determines the length of this character
size_t length = bit::lzcnt((~first) << 24);
if (unlikely(begin + length > end)) {
ch = uint32_t('?');
return end;
}
if (first < 0xE0) {
ch = ((uint32_t(begin[0]) & 0x1F) << 6)
| ((uint32_t(begin[1]) & 0x3F));
} else if (first < 0xF0) {
ch = ((uint32_t(begin[0]) & 0x0F) << 12)
| ((uint32_t(begin[1]) & 0x3F) << 6)
| ((uint32_t(begin[2]) & 0x3F));
} else if (first < 0xF8) {
ch = ((uint32_t(begin[0]) & 0x07) << 18)
| ((uint32_t(begin[1]) & 0x3F) << 12)
| ((uint32_t(begin[2]) & 0x3F) << 6)
| ((uint32_t(begin[3]) & 0x3F));
} else {
// Invalid prefix
ch = uint32_t('?');
}
return begin + length;
}
}
const uint16_t* decodeTypedChar(
const uint16_t* begin,
const uint16_t* end,
uint32_t& ch) {
uint32_t first = begin[0];
if (likely(first < 0xD800)) {
ch = first;
return begin + 1;
} else if (first < 0xDC00) {
if (unlikely(begin + 2 > end)) {
ch = uint32_t('?');
return end;
}
ch = 0x10000
+ ((uint32_t(begin[0]) & 0x3FF) << 10)
+ ((uint32_t(begin[1]) & 0x3FF));
return begin + 2;
} else if (unlikely(first < 0xE000)) {
// Stray low surrogate
ch = uint32_t('?');
return begin + 1;
} else {
ch = first;
return begin + 1;
}
}
const uint32_t* decodeTypedChar(
const uint32_t* begin,
const uint32_t* end,
uint32_t& ch) {
ch = begin[0];
return begin + 1;
}
size_t encodeTypedChar(
uint8_t* begin,
uint8_t* end,
uint32_t ch) {
if (likely(ch < 0x80)) {
if (begin) {
if (unlikely(begin + 1 > end))
return 0;
begin[0] = uint8_t(ch);
}
return 1;
} else if (ch < 0x800) {
if (begin) {
if (unlikely(begin + 2 > end))
return 0;
begin[0] = uint8_t(0xC0 | (ch >> 6));
begin[1] = uint8_t(0x80 | (ch & 0x3F));
}
return 2;
} else if (ch < 0x10000) {
if (begin) {
if (unlikely(begin + 3 > end))
return 0;
begin[0] = uint8_t(0xE0 | ((ch >> 12)));
begin[1] = uint8_t(0x80 | ((ch >> 6) & 0x3F));
begin[2] = uint8_t(0x80 | ((ch >> 0) & 0x3F));
}
return 3;
} else if (ch < 0x200000) {
if (begin) {
if (unlikely(begin + 4 > end))
return 0;
begin[0] = uint8_t(0xF0 | ((ch >> 18)));
begin[1] = uint8_t(0x80 | ((ch >> 12) & 0x3F));
begin[2] = uint8_t(0x80 | ((ch >> 6) & 0x3F));
begin[3] = uint8_t(0x80 | ((ch >> 0) & 0x3F));
}
return 4;
} else {
// Invalid code point for UTF-8
return 0;
}
}
size_t encodeTypedChar(
uint16_t* begin,
uint16_t* end,
uint32_t ch) {
if (likely(ch < 0xD800)) {
if (begin) {
if (unlikely(begin + 1 > end))
return 0;
begin[0] = ch;
}
return 1;
} else if (ch < 0xE000) {
// Private use code points,
// we can't encode these
return 0;
} else if (ch < 0x10000) {
if (begin) {
if (unlikely(begin + 1 > end))
return 0;
begin[0] = ch;
}
return 1;
} else if (ch < 0x110000) {
if (begin) {
if (unlikely(begin + 2 > end))
return 0;
ch -= 0x10000;
begin[0] = uint16_t(0xD800 + (ch >> 10));
begin[1] = uint16_t(0xDC00 + (ch & 0x3FF));
}
return 2;
} else {
// Invalid code point
return 0;
}
}
size_t encodeTypedChar(
uint32_t* begin,
uint32_t* end,
uint32_t ch) {
if (begin) {
if (unlikely(begin + 1 > end))
return 0;
begin[0] = ch;
}
return 1;
}
std::string fromws(const WCHAR* ws) {
size_t srcLen = length(ws);
size_t dstLen = transcodeString<char>(
nullptr, 0, ws, srcLen);
std::string result;
result.resize(dstLen);
transcodeString(result.data(),
dstLen, ws, srcLen);
return result;
}
std::wstring tows(const char* mbs) {
size_t srcLen = length(mbs);
size_t dstLen = transcodeString<wchar_t>(
nullptr, 0, mbs, srcLen);
std::wstring result;
result.resize(dstLen);
transcodeString(result.data(),
dstLen, mbs, srcLen);
return result;
}
}

242
src/util/util_string.h Normal file
View file

@ -0,0 +1,242 @@
#pragma once
#include <cstring>
#include <string>
#include <sstream>
#include <vector>
#include "./com/com_include.h"
#include "util_bit.h"
#include "util_likely.h"
namespace dxvk::str {
template<size_t S> struct UnicodeChar { };
template<> struct UnicodeChar<1> { using type = uint8_t; };
template<> struct UnicodeChar<2> { using type = uint16_t; };
template<> struct UnicodeChar<4> { using type = uint32_t; };
template<typename T>
using UnicodeCharType = typename UnicodeChar<sizeof(T)>::type;
const uint8_t* decodeTypedChar(
const uint8_t* begin,
const uint8_t* end,
uint32_t& ch);
const uint16_t* decodeTypedChar(
const uint16_t* begin,
const uint16_t* end,
uint32_t& ch);
const uint32_t* decodeTypedChar(
const uint32_t* begin,
const uint32_t* end,
uint32_t& ch);
size_t encodeTypedChar(
uint8_t* begin,
uint8_t* end,
uint32_t ch);
size_t encodeTypedChar(
uint16_t* begin,
uint16_t* end,
uint32_t ch);
size_t encodeTypedChar(
uint32_t* begin,
uint32_t* end,
uint32_t ch);
/**
* \brief Decodes a single character
*
* Note that \c begin and \c end must not be equal.
* \param [in] begin Pointer to current position within the input string
* \param [in] end Pointer to the end of the input string
* \param [out] ch Pointer to the decoded character code
* \returns Pointer to next character in the input string
*/
template<typename T>
const T* decodeChar(
const T* begin,
const T* end,
uint32_t& ch) {
using CharType = UnicodeCharType<T>;
const CharType* result = decodeTypedChar(
reinterpret_cast<const CharType*>(begin),
reinterpret_cast<const CharType*>(end),
ch);
return reinterpret_cast<const T*>(result);
}
/**
* \brief Encodes a character
*
* Note that \c begin and \c end may be both be \c nullptr or equal, in
* which case only the length of the encoded character will be returned.
* \param [in] begin Pointer to current position within the output string
* \param [in] end Pointer to the end of the output string
* \param [in] ch Character to encode
* \returns If begin is \c nullptr , the number of units required to encode
* the character. Otherwise, the number of units written to the output.
* This may return \c 0 for characters that cannot be written or encoded.
*/
template<typename T>
size_t encodeChar(
T* begin,
T* end,
uint32_t ch) {
using CharType = UnicodeCharType<T>;
return encodeTypedChar(
reinterpret_cast<CharType*>(begin),
reinterpret_cast<CharType*>(end),
ch);
}
/**
* \brief Computes length of a null-terminated string
*
* \param [in] begin Start of input string
* \returns Number of characters in input string,
* excluding the terminating null character
*/
template<typename S>
size_t length(const S* string) {
size_t result = 0;
while (string[result])
result += 1;
return result;
}
/**
* \brief Converts string from one encoding to another
*
* The output string arguments may be \c nullptr. In that case, the
* total length of the transcoded string will be returned, in units
* of the output character type. The output string will only be
* null-terminated if the input string is also null-terminated.
* \tparam D Output character type
* \tparam S Input character type
* \param [in] dstBegin Start of output string
* \param [in] dstLength Length of output string
* \param [in] srcBegin Start of input string
* \param [in] srcLength Length of input string
* \returns If \c dstBegin is \c nullptr , the total number of output
* characters required to store the output string. Otherwise, the
* total number of characters written to the output string.
*/
template<typename D, typename S>
size_t transcodeString(
D* dstBegin,
size_t dstLength,
const S* srcBegin,
size_t srcLength) {
size_t totalLength = 0;
auto dstEnd = dstBegin + dstLength;
auto srcEnd = srcBegin + srcLength;
while (srcBegin < srcEnd) {
uint32_t ch;
srcBegin = decodeChar<S>(srcBegin, srcEnd, ch);
if (dstBegin)
totalLength += encodeChar<D>(dstBegin + totalLength, dstEnd, ch);
else
totalLength += encodeChar<D>(nullptr, nullptr, ch);
if (!ch)
break;
}
return totalLength;
}
/**
* \brief Creates string object from wide char array
*
* \param [in] ws Null-terminated wide string
* \returns Regular string object
*/
std::string fromws(const WCHAR* ws);
/**
* \brief Creates wide string object from char array
*
* \param [in] mbs Null-terminated string
* \returns Wide string object
*/
std::wstring tows(const char* mbs);
#ifdef _WIN32
using path_string = std::wstring;
inline path_string topath(const char* mbs) { return tows(mbs); }
#else
using path_string = std::string;
inline path_string topath(const char* mbs) { return std::string(mbs); }
#endif
inline void format1(std::stringstream&) { }
template<typename... Tx>
void format1(std::stringstream& str, const WCHAR *arg, const Tx&... args) {
str << fromws(arg);
format1(str, args...);
}
template<typename T, typename... Tx>
void format1(std::stringstream& str, const T& arg, const Tx&... args) {
str << arg;
format1(str, args...);
}
template<typename... Args>
std::string format(const Args&... args) {
std::stringstream stream;
format1(stream, args...);
return stream.str();
}
inline void strlcpy(char* dst, const char* src, size_t count) {
if (count > 0) {
std::strncpy(dst, src, count - 1);
dst[count - 1] = '\0';
}
}
/**
* \brief Split string at one or more delimiters characters
*
* \param [in] string String to split
* \param [in] delims Delimiter characters
* \returns Vector of substring views
*/
inline std::vector<std::string_view> split(std::string_view string, std::string_view delims = " ") {
std::vector<std::string_view> tokens;
for (size_t start = 0; start < string.size(); ) {
// Find first delimiter
const auto end = string.find_first_of(delims, start);
// Add non-empty tokens
if (start != end)
tokens.emplace_back(string.substr(start, end-start));
// Break at the end of string
if (end == std::string_view::npos)
break;
start = end + 1;
}
return tokens;
}
}