diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj
index 0dde566e5aa6..4d272f1cd8ef 100644
--- a/Source/Core/DolphinQt/DolphinQt.vcxproj
+++ b/Source/Core/DolphinQt/DolphinQt.vcxproj
@@ -472,6 +472,9 @@
{4c3b2264-ea73-4a7b-9cfe-65b0fd635ebb}
+
+ {570215b7-e32f-4438-95ae-c8d955f9fca3}
+
diff --git a/Source/Core/VideoBackends/D3D12/BoundingBox.cpp b/Source/Core/VideoBackends/D3D12/BoundingBox.cpp
new file mode 100644
index 000000000000..af85c3ee4519
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/BoundingBox.cpp
@@ -0,0 +1,181 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "VideoBackends/D3D12/BoundingBox.h"
+#include "Common/Logging/Log.h"
+#include "VideoBackends/D3D12/DXContext.h"
+#include "VideoBackends/D3D12/Renderer.h"
+#include "VideoBackends/D3D12/StreamBuffer.h"
+
+// TODO: Use write 32-bit value to buffer in command list where available to skip stream buffer.
+namespace DX12
+{
+BoundingBox::BoundingBox() = default;
+
+BoundingBox::~BoundingBox()
+{
+ if (m_gpu_descriptor)
+ g_context->GetDescriptorHeapManager()->Free(m_gpu_descriptor);
+}
+
+std::unique_ptr BoundingBox::Create()
+{
+ auto bbox = std::unique_ptr(new BoundingBox());
+ if (!bbox->CreateBuffers())
+ return nullptr;
+
+ return bbox;
+}
+
+bool BoundingBox::CreateBuffers()
+{
+ static constexpr D3D12_HEAP_PROPERTIES gpu_heap_properties = {
+ D3D12_HEAP_TYPE_DEFAULT, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1, 1};
+ static constexpr D3D12_HEAP_PROPERTIES cpu_heap_properties = {
+ D3D12_HEAP_TYPE_READBACK, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1, 1};
+ D3D12_RESOURCE_DESC buffer_desc = {D3D12_RESOURCE_DIMENSION_BUFFER,
+ 0,
+ BUFFER_SIZE,
+ 1,
+ 1,
+ 1,
+ DXGI_FORMAT_UNKNOWN,
+ {1, 0},
+ D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
+ D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS};
+
+ HRESULT hr = g_context->GetDevice()->CreateCommittedResource(
+ &gpu_heap_properties, D3D12_HEAP_FLAG_NONE, &buffer_desc,
+ D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, IID_PPV_ARGS(&m_gpu_buffer));
+ CHECK(SUCCEEDED(hr), "Creating bounding box GPU buffer failed");
+ if (FAILED(hr) || !g_context->GetDescriptorHeapManager()->Allocate(&m_gpu_descriptor))
+ return false;
+
+ D3D12_UNORDERED_ACCESS_VIEW_DESC uav_desc = {DXGI_FORMAT_R32_SINT, D3D12_UAV_DIMENSION_BUFFER};
+ uav_desc.Buffer.NumElements = NUM_VALUES;
+ g_context->GetDevice()->CreateUnorderedAccessView(m_gpu_buffer.Get(), nullptr, &uav_desc,
+ m_gpu_descriptor.cpu_handle);
+ g_context->GetDevice()->CreateUnorderedAccessView(m_gpu_buffer.Get(), nullptr, &uav_desc,
+ m_gpu_descriptor.cpu_handle_shadow);
+
+ buffer_desc.Flags = D3D12_RESOURCE_FLAG_NONE;
+ hr = g_context->GetDevice()->CreateCommittedResource(&cpu_heap_properties, D3D12_HEAP_FLAG_NONE,
+ &buffer_desc, D3D12_RESOURCE_STATE_COPY_DEST,
+ nullptr, IID_PPV_ARGS(&m_readback_buffer));
+ CHECK(SUCCEEDED(hr), "Creating bounding box CPU buffer failed");
+ if (FAILED(hr))
+ return false;
+
+ m_stream_buffer = StreamBuffer::Create(STREAM_BUFFER_SIZE);
+ if (!m_stream_buffer)
+ return false;
+
+ // Both the CPU and GPU buffer's contents is unknown, so force a flush the first time.
+ m_values.fill(0);
+ m_dirty.fill(true);
+ m_valid = true;
+ return true;
+}
+
+void BoundingBox::Readback()
+{
+ // Copy from GPU->CPU buffer, and wait for the GPU to finish the copy.
+ D3D::ResourceBarrier(g_context->GetCommandList(), m_gpu_buffer.Get(),
+ D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
+ g_context->GetCommandList()->CopyBufferRegion(m_readback_buffer.Get(), 0, m_gpu_buffer.Get(), 0,
+ BUFFER_SIZE);
+ D3D::ResourceBarrier(g_context->GetCommandList(), m_gpu_buffer.Get(),
+ D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+ Renderer::GetInstance()->ExecuteCommandList(true);
+
+ // Read back to cached values.
+ static constexpr D3D12_RANGE read_range = {0, BUFFER_SIZE};
+ void* mapped_pointer;
+ HRESULT hr = m_readback_buffer->Map(0, &read_range, &mapped_pointer);
+ CHECK(SUCCEEDED(hr), "Map bounding box CPU buffer");
+ if (FAILED(hr))
+ return;
+
+ static constexpr D3D12_RANGE write_range = {0, 0};
+ std::array new_values;
+ std::memcpy(new_values.data(), mapped_pointer, BUFFER_SIZE);
+ m_readback_buffer->Unmap(0, &write_range);
+
+ // Preserve dirty values, that way we don't need to sync.
+ for (u32 i = 0; i < NUM_VALUES; i++)
+ {
+ if (!m_dirty[i])
+ m_values[i] = new_values[i];
+ }
+ m_valid = true;
+}
+
+s32 BoundingBox::Get(size_t index)
+{
+ if (!m_valid)
+ Readback();
+
+ return m_values[index];
+}
+
+void BoundingBox::Set(size_t index, s32 value)
+{
+ m_values[index] = value;
+ m_dirty[index] = true;
+}
+
+void BoundingBox::Invalidate()
+{
+ m_dirty.fill(false);
+ m_valid = false;
+}
+
+void BoundingBox::Flush()
+{
+ // Reserve space for all values, even we only copy some.
+ if (!m_stream_buffer->ReserveMemory(BUFFER_SIZE, BUFFER_SIZE))
+ {
+ WARN_LOG(VIDEO, "Executing command list while waiting for space in the bbox stream buffer");
+ if (!m_stream_buffer->ReserveMemory(BUFFER_SIZE, BUFFER_SIZE))
+ {
+ PanicAlert("Failed to reserve bbox stream buffer memory");
+ return;
+ }
+ }
+ const u32 src_buffer_offset = m_stream_buffer->GetCurrentOffset();
+ std::memcpy(m_stream_buffer->GetCurrentHostPointer(), m_values.data(), BUFFER_SIZE);
+ m_stream_buffer->CommitMemory(BUFFER_SIZE);
+
+ D3D::ResourceBarrier(g_context->GetCommandList(), m_gpu_buffer.Get(),
+ D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_DEST);
+
+ // Try to batch updates together, but don't overwrite non-dirty values.
+ // It's likely all of them will be written anyway.
+ for (u32 start = 0; start < NUM_VALUES;)
+ {
+ if (!m_dirty[start])
+ {
+ start++;
+ continue;
+ }
+
+ u32 end = start + 1;
+ for (; end < NUM_VALUES; end++)
+ {
+ if (!m_dirty[end])
+ break;
+ }
+
+ const u32 count = end - start;
+ g_context->GetCommandList()->CopyBufferRegion(
+ m_gpu_buffer.Get(), 0, m_stream_buffer->GetBuffer(), src_buffer_offset, BUFFER_SIZE);
+
+ std::fill_n(&m_dirty[start], count, false);
+ start = end;
+ }
+
+ D3D::ResourceBarrier(g_context->GetCommandList(), m_gpu_buffer.Get(),
+ D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+}
+}; // namespace DX12
diff --git a/Source/Core/VideoBackends/D3D12/BoundingBox.h b/Source/Core/VideoBackends/D3D12/BoundingBox.h
new file mode 100644
index 000000000000..67c2ba17373f
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/BoundingBox.h
@@ -0,0 +1,50 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+#include
+#include "VideoBackends/D3D12/D3DBase.h"
+#include "VideoBackends/D3D12/DescriptorHeapManager.h"
+
+namespace DX12
+{
+class StreamBuffer;
+class BoundingBox
+{
+public:
+ ~BoundingBox();
+
+ static std::unique_ptr Create();
+
+ const DescriptorHandle& GetGPUDescriptor() const { return m_gpu_descriptor; }
+
+ s32 Get(size_t index);
+ void Set(size_t index, s32 value);
+
+ void Invalidate();
+ void Flush();
+
+private:
+ static const u32 NUM_VALUES = 4;
+ static const u32 BUFFER_SIZE = sizeof(u32) * NUM_VALUES;
+ static const u32 MAX_UPDATES_PER_FRAME = 128;
+ static const u32 STREAM_BUFFER_SIZE = BUFFER_SIZE * MAX_UPDATES_PER_FRAME;
+
+ BoundingBox();
+
+ bool CreateBuffers();
+ void Readback();
+
+ // Three buffers: One on the GPU for read/write, one on the CPU for reading back, and a third for
+ // streaming changes. We use a stream buffer so we don't need to block when two bbox updates are
+ // within a single command buffer.
+ ComPtr m_gpu_buffer;
+ ComPtr m_readback_buffer;
+ std::unique_ptr m_stream_buffer;
+ DescriptorHandle m_gpu_descriptor;
+ std::array m_values = {};
+ std::array m_dirty = {};
+ bool m_valid = true;
+};
+}; // namespace DX12
diff --git a/Source/Core/VideoBackends/D3D12/D3D12.vcxproj b/Source/Core/VideoBackends/D3D12/D3D12.vcxproj
new file mode 100644
index 000000000000..0a2214539183
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/D3D12.vcxproj
@@ -0,0 +1,90 @@
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {570215B7-E32F-4438-95AE-C8D955F9FCA3}
+ 10.0.17134.0
+
+
+
+ StaticLibrary
+ v141
+ Unicode
+
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ NotUsing
+
+
+
+
+
+ NotUsing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {3de9ee35-3e91-4f27-a014-2866ad8c3fe3}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/Core/VideoBackends/D3D12/D3D12.vcxproj.filters b/Source/Core/VideoBackends/D3D12/D3D12.vcxproj.filters
new file mode 100644
index 000000000000..b60fb58481d4
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/D3D12.vcxproj.filters
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/Core/VideoBackends/D3D12/D3DBase.cpp b/Source/Core/VideoBackends/D3D12/D3DBase.cpp
new file mode 100644
index 000000000000..ba5683685df9
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/D3DBase.cpp
@@ -0,0 +1,297 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "VideoBackends/D3D12/D3DBase.h"
+#include "Common/Assert.h"
+#include "Common/MsgHandler.h"
+#include "VideoBackends/D3D12/DXContext.h"
+#include "VideoCommon/TextureConfig.h"
+#include "VideoCommon/VideoConfig.h"
+
+namespace DX12
+{
+// d3dcompiler_*.dll exports
+static HINSTANCE s_d3d_compiler_dll = nullptr;
+static int s_d3d_compiler_dll_ref = 0;
+pD3DCompile d3d_compile = nullptr;
+
+// dxgi.dll exports
+static HINSTANCE s_dxgi_dll = nullptr;
+static int s_dxgi_dll_ref = 0;
+CREATEDXGIFACTORY create_dxgi_factory = nullptr;
+
+// d3d12.dll exports
+static HINSTANCE s_d3d12_dll = nullptr;
+static int s_d3d12_dll_ref = 0;
+D3D12CREATEDEVICE d3d12_create_device = nullptr;
+D3D12SERIALIZEROOTSIGNATURE d3d12_serialize_root_signature = nullptr;
+D3D12GETDEBUGINTERFACE d3d12_get_debug_interface = nullptr;
+
+namespace D3D
+{
+bool LoadDXGI()
+{
+ if (s_dxgi_dll_ref++ > 0)
+ return true;
+
+ s_dxgi_dll = LoadLibraryA("dxgi.dll");
+ if (!s_dxgi_dll)
+ {
+ PanicAlert("Failed to load dxgi.dll");
+ --s_dxgi_dll_ref;
+ return false;
+ }
+
+ create_dxgi_factory = (CREATEDXGIFACTORY)GetProcAddress(s_dxgi_dll, "CreateDXGIFactory");
+ CHECK(create_dxgi_factory, "CreateDXGIFactory() exists");
+ if (!create_dxgi_factory)
+ return false;
+
+ return true;
+}
+
+bool LoadD3D()
+{
+ if (s_d3d12_dll_ref++ > 0)
+ return true;
+
+ s_d3d12_dll = LoadLibraryA("d3d12.dll");
+ if (!s_d3d12_dll)
+ {
+ PanicAlert("Failed to load d3d12.dll");
+ return false;
+ }
+
+ d3d12_create_device = (D3D12CREATEDEVICE)GetProcAddress(s_d3d12_dll, "D3D12CreateDevice");
+ CHECK(d3d12_create_device, "D3D12CreateDevice() exists");
+ if (!d3d12_create_device)
+ return false;
+
+ d3d12_serialize_root_signature =
+ (D3D12SERIALIZEROOTSIGNATURE)GetProcAddress(s_d3d12_dll, "D3D12SerializeRootSignature");
+ CHECK(d3d12_serialize_root_signature, "D3D12SerializeRootSignature() exists");
+ if (!d3d12_serialize_root_signature)
+ return false;
+
+ d3d12_get_debug_interface =
+ (D3D12GETDEBUGINTERFACE)GetProcAddress(s_d3d12_dll, "D3D12GetDebugInterface");
+ CHECK(d3d12_get_debug_interface, "D3D12GetDebugInterface() exists");
+ if (!d3d12_get_debug_interface)
+ return false;
+
+ return true;
+}
+
+bool LoadD3DCompiler()
+{
+ if (s_d3d_compiler_dll_ref++ > 0)
+ return true;
+
+ s_d3d_compiler_dll = LoadLibraryA(D3DCOMPILER_DLL_A);
+ if (!s_d3d_compiler_dll)
+ {
+ PanicAlert("Failed to load " D3DCOMPILER_DLL_A);
+ return false;
+ }
+
+ d3d_compile = (pD3DCompile)GetProcAddress(s_d3d_compiler_dll, "D3DCompile");
+ CHECK(d3d_compile, "D3DCompile() function exists");
+ if (!d3d_compile)
+ return false;
+
+ return true;
+}
+
+void UnloadDXGI()
+{
+ ASSERT(s_dxgi_dll_ref > 0);
+ if (--s_dxgi_dll_ref != 0)
+ return;
+
+ if (s_dxgi_dll)
+ FreeLibrary(s_dxgi_dll);
+
+ s_dxgi_dll = nullptr;
+ create_dxgi_factory = nullptr;
+}
+
+void UnloadD3D()
+{
+ ASSERT(s_d3d12_dll_ref > 0);
+ if (--s_d3d12_dll_ref != 0)
+ return;
+
+ if (s_d3d12_dll)
+ FreeLibrary(s_d3d12_dll);
+
+ s_d3d12_dll = nullptr;
+ d3d12_create_device = nullptr;
+ d3d12_serialize_root_signature = nullptr;
+}
+
+void UnloadD3DCompiler()
+{
+ ASSERT(s_d3d_compiler_dll_ref > 0);
+ if (--s_d3d_compiler_dll_ref != 0)
+ return;
+
+ if (s_d3d_compiler_dll)
+ FreeLibrary(s_d3d_compiler_dll);
+
+ s_d3d_compiler_dll = nullptr;
+ d3d_compile = nullptr;
+}
+
+DXGI_FORMAT GetDXGIFormatForAbstractFormat(AbstractTextureFormat format, bool typeless)
+{
+ switch (format)
+ {
+ case AbstractTextureFormat::DXT1:
+ return DXGI_FORMAT_BC1_UNORM;
+ case AbstractTextureFormat::DXT3:
+ return DXGI_FORMAT_BC2_UNORM;
+ case AbstractTextureFormat::DXT5:
+ return DXGI_FORMAT_BC3_UNORM;
+ case AbstractTextureFormat::BPTC:
+ return DXGI_FORMAT_BC7_UNORM;
+ case AbstractTextureFormat::RGBA8:
+ return typeless ? DXGI_FORMAT_R8G8B8A8_TYPELESS : DXGI_FORMAT_R8G8B8A8_UNORM;
+ case AbstractTextureFormat::BGRA8:
+ return typeless ? DXGI_FORMAT_B8G8R8A8_TYPELESS : DXGI_FORMAT_B8G8R8A8_UNORM;
+ case AbstractTextureFormat::R16:
+ return typeless ? DXGI_FORMAT_R16_TYPELESS : DXGI_FORMAT_R16_UNORM;
+ case AbstractTextureFormat::R32F:
+ return typeless ? DXGI_FORMAT_R32_TYPELESS : DXGI_FORMAT_R32_FLOAT;
+ case AbstractTextureFormat::D16:
+ return DXGI_FORMAT_R16_TYPELESS;
+ case AbstractTextureFormat::D24_S8:
+ return DXGI_FORMAT_R24G8_TYPELESS;
+ case AbstractTextureFormat::D32F:
+ return DXGI_FORMAT_R32_TYPELESS;
+ case AbstractTextureFormat::D32F_S8:
+ return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
+ default:
+ PanicAlert("Unhandled texture format.");
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+DXGI_FORMAT GetSRVFormatForAbstractFormat(AbstractTextureFormat format)
+{
+ switch (format)
+ {
+ case AbstractTextureFormat::DXT1:
+ return DXGI_FORMAT_BC1_UNORM;
+ case AbstractTextureFormat::DXT3:
+ return DXGI_FORMAT_BC2_UNORM;
+ case AbstractTextureFormat::DXT5:
+ return DXGI_FORMAT_BC3_UNORM;
+ case AbstractTextureFormat::BPTC:
+ return DXGI_FORMAT_BC7_UNORM;
+ case AbstractTextureFormat::RGBA8:
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ case AbstractTextureFormat::BGRA8:
+ return DXGI_FORMAT_B8G8R8A8_UNORM;
+ case AbstractTextureFormat::R16:
+ return DXGI_FORMAT_R16_UNORM;
+ case AbstractTextureFormat::R32F:
+ return DXGI_FORMAT_R32_FLOAT;
+ case AbstractTextureFormat::D16:
+ return DXGI_FORMAT_R16_UNORM;
+ case AbstractTextureFormat::D24_S8:
+ return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
+ case AbstractTextureFormat::D32F:
+ return DXGI_FORMAT_R32_FLOAT;
+ case AbstractTextureFormat::D32F_S8:
+ return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
+ default:
+ PanicAlert("Unhandled SRV format");
+ return DXGI_FORMAT_UNKNOWN;
+ }
+}
+
+DXGI_FORMAT GetRTVFormatForAbstractFormat(AbstractTextureFormat format, bool integer)
+{
+ switch (format)
+ {
+ case AbstractTextureFormat::RGBA8:
+ return integer ? DXGI_FORMAT_R8G8B8A8_UINT : DXGI_FORMAT_R8G8B8A8_UNORM;
+ case AbstractTextureFormat::BGRA8:
+ return DXGI_FORMAT_B8G8R8A8_UNORM;
+ case AbstractTextureFormat::R16:
+ return integer ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R16_UNORM;
+ case AbstractTextureFormat::R32F:
+ return DXGI_FORMAT_R32_FLOAT;
+ default:
+ PanicAlert("Unhandled RTV format");
+ return DXGI_FORMAT_UNKNOWN;
+ }
+}
+DXGI_FORMAT GetDSVFormatForAbstractFormat(AbstractTextureFormat format)
+{
+ switch (format)
+ {
+ case AbstractTextureFormat::D16:
+ return DXGI_FORMAT_D16_UNORM;
+ case AbstractTextureFormat::D24_S8:
+ return DXGI_FORMAT_D24_UNORM_S8_UINT;
+ case AbstractTextureFormat::D32F:
+ return DXGI_FORMAT_D32_FLOAT;
+ case AbstractTextureFormat::D32F_S8:
+ return DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
+ default:
+ PanicAlert("Unhandled DSV format");
+ return DXGI_FORMAT_UNKNOWN;
+ }
+}
+
+AbstractTextureFormat GetAbstractFormatForDXGIFormat(DXGI_FORMAT format)
+{
+ switch (format)
+ {
+ case DXGI_FORMAT_R8G8B8A8_UINT:
+ case DXGI_FORMAT_R8G8B8A8_UNORM:
+ case DXGI_FORMAT_R8G8B8A8_TYPELESS:
+ return AbstractTextureFormat::RGBA8;
+
+ case DXGI_FORMAT_B8G8R8A8_UNORM:
+ case DXGI_FORMAT_B8G8R8A8_TYPELESS:
+ return AbstractTextureFormat::BGRA8;
+
+ case DXGI_FORMAT_R16_UINT:
+ case DXGI_FORMAT_R16_UNORM:
+ case DXGI_FORMAT_R16_TYPELESS:
+ return AbstractTextureFormat::R16;
+
+ case DXGI_FORMAT_R32_FLOAT:
+ case DXGI_FORMAT_R32_TYPELESS:
+ return AbstractTextureFormat::R32F;
+
+ case DXGI_FORMAT_D16_UNORM:
+ return AbstractTextureFormat::D16;
+
+ case DXGI_FORMAT_D24_UNORM_S8_UINT:
+ return AbstractTextureFormat::D24_S8;
+
+ case DXGI_FORMAT_D32_FLOAT:
+ return AbstractTextureFormat::D32F;
+
+ case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
+ return AbstractTextureFormat::D32F_S8;
+
+ case DXGI_FORMAT_BC1_UNORM:
+ return AbstractTextureFormat::DXT1;
+ case DXGI_FORMAT_BC2_UNORM:
+ return AbstractTextureFormat::DXT3;
+ case DXGI_FORMAT_BC3_UNORM:
+ return AbstractTextureFormat::DXT5;
+ case DXGI_FORMAT_BC7_UNORM:
+ return AbstractTextureFormat::BPTC;
+
+ default:
+ return AbstractTextureFormat::Undefined;
+ }
+}
+} // namespace D3D
+
+} // namespace DX12
diff --git a/Source/Core/VideoBackends/D3D12/D3DBase.h b/Source/Core/VideoBackends/D3D12/D3DBase.h
new file mode 100644
index 000000000000..ecbc3d83811a
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/D3DBase.h
@@ -0,0 +1,82 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Common/Common.h"
+#include "Common/CommonTypes.h"
+#include "Common/MsgHandler.h"
+#include "VideoCommon/VideoCommon.h"
+
+enum class AbstractTextureFormat : u32;
+
+namespace DX12
+{
+using Microsoft::WRL::ComPtr;
+
+#define CHECK(cond, Message, ...) \
+ if (!(cond)) \
+ { \
+ __debugbreak(); \
+ PanicAlert(__FUNCTION__ " failed in %s at line %d: " Message, __FILE__, __LINE__, \
+ __VA_ARGS__); \
+ }
+
+// TODO: Move this rubbish out
+namespace D3D
+{
+bool LoadDXGI();
+bool LoadD3D();
+bool LoadD3DCompiler();
+void UnloadDXGI();
+void UnloadD3D();
+void UnloadD3DCompiler();
+
+DXGI_FORMAT GetDXGIFormatForAbstractFormat(AbstractTextureFormat format, bool typeless);
+DXGI_FORMAT GetSRVFormatForAbstractFormat(AbstractTextureFormat format);
+DXGI_FORMAT GetRTVFormatForAbstractFormat(AbstractTextureFormat format, bool integer);
+DXGI_FORMAT GetDSVFormatForAbstractFormat(AbstractTextureFormat format);
+AbstractTextureFormat GetAbstractFormatForDXGIFormat(DXGI_FORMAT format);
+
+inline D3D12_BOX RectangleToBox(const MathUtil::Rectangle& rc)
+{
+ return D3D12_BOX{static_cast(rc.left), static_cast(rc.top), 0,
+ static_cast(rc.right), static_cast(rc.bottom), 1};
+}
+
+inline void ResourceBarrier(ID3D12GraphicsCommandList* cmdlist, ID3D12Resource* resource,
+ D3D12_RESOURCE_STATES from_state, D3D12_RESOURCE_STATES to_state)
+{
+ const D3D12_RESOURCE_BARRIER barrier = {
+ D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
+ D3D12_RESOURCE_BARRIER_FLAG_NONE,
+ {{resource, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, from_state, to_state}}};
+ cmdlist->ResourceBarrier(1, &barrier);
+}
+
+} // namespace D3D
+
+using CREATEDXGIFACTORY = HRESULT(WINAPI*)(REFIID, void**);
+extern CREATEDXGIFACTORY create_dxgi_factory;
+
+using D3D12CREATEDEVICE = HRESULT(WINAPI*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**);
+extern D3D12CREATEDEVICE d3d12_create_device;
+
+using D3D12SERIALIZEROOTSIGNATURE =
+ HRESULT(WINAPI*)(const D3D12_ROOT_SIGNATURE_DESC* pRootSignature,
+ D3D_ROOT_SIGNATURE_VERSION Version, ID3DBlob** ppBlob, ID3DBlob** ppErrorBlob);
+extern D3D12SERIALIZEROOTSIGNATURE d3d12_serialize_root_signature;
+
+using D3D12GETDEBUGINTERFACE = HRESULT(WINAPI*)(REFIID riid, void** ppvDebug);
+extern D3D12GETDEBUGINTERFACE d3d12_get_debug_interface;
+
+extern pD3DCompile d3d_compile;
+
+} // namespace DX12
diff --git a/Source/Core/VideoBackends/D3D12/DXContext.cpp b/Source/Core/VideoBackends/D3D12/DXContext.cpp
new file mode 100644
index 000000000000..dfe473f29d03
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/DXContext.cpp
@@ -0,0 +1,509 @@
+// Copyright 2015 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+#include
+#include
+
+#include "Common/Assert.h"
+#include "Common/StringUtil.h"
+#include "VideoBackends/D3D12/D3DBase.h"
+#include "VideoBackends/D3D12/DXContext.h"
+#include "VideoBackends/D3D12/DescriptorHeapManager.h"
+#include "VideoBackends/D3D12/StreamBuffer.h"
+
+namespace DX12
+{
+std::unique_ptr g_context;
+
+DXContext::DXContext() = default;
+
+DXContext::~DXContext()
+{
+ if (m_fence_event)
+ CloseHandle(m_fence_event);
+}
+
+std::vector DXContext::GetAdapterNames()
+{
+ if (!D3D::LoadDXGI())
+ return {};
+
+ ComPtr factory;
+ HRESULT hr = create_dxgi_factory(IID_PPV_ARGS(&factory));
+ if (!SUCCEEDED(hr))
+ return {};
+
+ std::vector adapters;
+ IDXGIAdapter* adapter;
+ while (factory->EnumAdapters(static_cast(adapters.size()), &adapter) !=
+ DXGI_ERROR_NOT_FOUND)
+ {
+ std::string name;
+ DXGI_ADAPTER_DESC desc;
+ if (SUCCEEDED(adapter->GetDesc(&desc)))
+ name = UTF16ToUTF8(desc.Description);
+
+ adapters.push_back(std::move(name));
+ }
+
+ factory.Reset();
+ D3D::UnloadDXGI();
+ return adapters;
+}
+
+std::vector DXContext::GetAAModes()
+{
+ if (!D3D::LoadD3D())
+ return {};
+
+ ComPtr device;
+ HRESULT hr = d3d12_create_device(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device));
+ if (!SUCCEEDED(hr))
+ return {};
+
+ std::vector aa_modes;
+ for (u32 samples = 1; samples < D3D12_MAX_MULTISAMPLE_SAMPLE_COUNT; ++samples)
+ {
+ D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS multisample_quality_levels = {};
+ multisample_quality_levels.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ multisample_quality_levels.SampleCount = samples;
+
+ device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
+ &multisample_quality_levels, sizeof(multisample_quality_levels));
+
+ if (multisample_quality_levels.NumQualityLevels > 0)
+ aa_modes.push_back(samples);
+ }
+
+ device.Reset();
+ D3D::UnloadD3D();
+ return aa_modes;
+}
+
+bool DXContext::Create(int adapter_index, bool enable_debug_layer)
+{
+ ASSERT(!g_context);
+ if (!D3D::LoadDXGI())
+ return false;
+
+ if (!D3D::LoadD3D())
+ {
+ D3D::UnloadDXGI();
+ return false;
+ }
+
+ if (!D3D::LoadD3DCompiler())
+ {
+ D3D::UnloadD3D();
+ D3D::UnloadDXGI();
+ return false;
+ }
+
+ g_context.reset(new DXContext());
+ if (!g_context->CreateDXGIFactory() ||
+ !g_context->CreateDevice(adapter_index, enable_debug_layer) ||
+ !g_context->CreateCommandQueue() || !g_context->CreateFence() ||
+ !g_context->CreateDescriptorHeaps() || !g_context->CreateRootSignatures() ||
+ !g_context->CreateTextureUploadBuffer() || !g_context->CreateCommandLists())
+ {
+ Destroy();
+ return false;
+ }
+
+ return true;
+}
+
+void DXContext::Destroy()
+{
+ // We have to clean up some things manually, because they reference back to us.
+ if (g_context)
+ {
+ g_context->m_texture_upload_buffer.reset();
+ g_context.reset();
+ }
+
+ D3D::UnloadD3DCompiler();
+ D3D::UnloadD3D();
+ D3D::UnloadDXGI();
+}
+
+bool DXContext::CreateDXGIFactory()
+{
+ HRESULT hr = create_dxgi_factory(IID_PPV_ARGS(&m_dxgi_factory));
+ CHECK(SUCCEEDED(hr), "Create DXGI factory");
+ return SUCCEEDED(hr);
+}
+
+bool DXContext::CreateDevice(int adapter_index, bool enable_debug_layer)
+{
+ IDXGIAdapter* adapter;
+ HRESULT hr = m_dxgi_factory->EnumAdapters(static_cast(adapter_index), &adapter);
+ if (FAILED(hr))
+ {
+ ERROR_LOG(VIDEO, "Adapter %d not found, using default", adapter_index);
+ adapter = nullptr;
+ }
+
+ // Enabling the debug layer will fail if the Graphics Tools feature is not installed.
+ if (enable_debug_layer)
+ {
+ hr = d3d12_get_debug_interface(IID_PPV_ARGS(&m_debug_interface));
+ if (SUCCEEDED(hr))
+ {
+ m_debug_interface->EnableDebugLayer();
+ }
+ else
+ {
+ ERROR_LOG(VIDEO, "Debug layer requested but not available.");
+ enable_debug_layer = false;
+ }
+ }
+
+ // Create the actual device.
+ hr = d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
+ CHECK(SUCCEEDED(hr), "Create D3D12 device");
+ if (FAILED(hr))
+ return false;
+
+ if (enable_debug_layer)
+ {
+ ComPtr info_queue;
+ if (SUCCEEDED(m_device->QueryInterface(IID_PPV_ARGS(&info_queue))))
+ {
+ info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
+ info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);
+
+ D3D12_INFO_QUEUE_FILTER filter = {};
+ D3D12_MESSAGE_ID id_list[] = {
+ D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE,
+ D3D12_MESSAGE_ID_CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE,
+ D3D12_MESSAGE_ID_CREATEGRAPHICSPIPELINESTATE_RENDERTARGETVIEW_NOT_SET,
+ D3D12_MESSAGE_ID_CREATEINPUTLAYOUT_TYPE_MISMATCH,
+ D3D12_MESSAGE_ID_DRAW_EMPTY_SCISSOR_RECTANGLE};
+ filter.DenyList.NumIDs = static_cast(ArraySize(id_list));
+ filter.DenyList.pIDList = id_list;
+ info_queue->PushStorageFilter(&filter);
+ }
+ }
+
+ return true;
+}
+
+bool DXContext::CreateCommandQueue()
+{
+ const D3D12_COMMAND_QUEUE_DESC queue_desc = {D3D12_COMMAND_LIST_TYPE_DIRECT,
+ D3D12_COMMAND_QUEUE_PRIORITY_NORMAL,
+ D3D12_COMMAND_QUEUE_FLAG_NONE, 1};
+ HRESULT hr = m_device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&m_command_queue));
+ CHECK(SUCCEEDED(hr), "Create command queue");
+ return SUCCEEDED(hr);
+}
+
+bool DXContext::CreateFence()
+{
+ HRESULT hr =
+ m_device->CreateFence(m_last_fence_value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
+ CHECK(SUCCEEDED(hr), "Create fence");
+ if (FAILED(hr))
+ return false;
+
+ m_fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ CHECK(m_fence_event != NULL, "Create fence event");
+ if (!m_fence_event)
+ return false;
+
+ return true;
+}
+
+bool DXContext::CreateDescriptorHeaps()
+{
+ static constexpr size_t MAX_ACTIVE_TEXTURES = 8192;
+ static constexpr size_t MAX_DRAWS_PER_FRAME = 8192;
+ static constexpr size_t TEMPORARY_SLOTS = MAX_DRAWS_PER_FRAME * 8;
+ static constexpr size_t MAX_RTVS = 4096;
+ static constexpr size_t MAX_DSVS = 128;
+ static constexpr size_t MAX_SAMPLERS = 2048;
+
+ m_descriptor_heap_manager =
+ DescriptorHeapManager::Create(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
+ D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
+ TEMPORARY_SLOTS + MAX_ACTIVE_TEXTURES, TEMPORARY_SLOTS, true);
+ m_rtv_heap_manager = DescriptorHeapManager::Create(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
+ D3D12_DESCRIPTOR_HEAP_FLAG_NONE, MAX_RTVS, 0);
+ m_dsv_heap_manager = DescriptorHeapManager::Create(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
+ D3D12_DESCRIPTOR_HEAP_FLAG_NONE, MAX_DSVS, 0);
+ m_sampler_heap_manager = SamplerHeapManager::Create(m_device.Get(), MAX_SAMPLERS);
+
+ if (!m_descriptor_heap_manager || !m_rtv_heap_manager || !m_dsv_heap_manager ||
+ !m_sampler_heap_manager)
+ {
+ return false;
+ }
+
+ m_gpu_descriptor_heaps[0] = m_descriptor_heap_manager->GetDescriptorHeap();
+ m_gpu_descriptor_heaps[1] = m_sampler_heap_manager->GetDescriptorHeap();
+ return true;
+}
+
+static void SetRootParamCBV(D3D12_ROOT_PARAMETER* rp, u32 shader_reg,
+ D3D12_SHADER_VISIBILITY visibility)
+{
+ rp->ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
+ rp->Descriptor.ShaderRegister = shader_reg;
+ rp->Descriptor.RegisterSpace = 0;
+ rp->ShaderVisibility = visibility;
+}
+
+static void SetRootParamTable(D3D12_ROOT_PARAMETER* rp, D3D12_DESCRIPTOR_RANGE* dr,
+ D3D12_DESCRIPTOR_RANGE_TYPE rt, u32 start_shader_reg,
+ u32 num_shader_regs, D3D12_SHADER_VISIBILITY visibility)
+{
+ dr->RangeType = rt;
+ dr->NumDescriptors = num_shader_regs;
+ dr->BaseShaderRegister = start_shader_reg;
+ dr->RegisterSpace = 0;
+ dr->OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
+
+ rp->ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+ rp->DescriptorTable.pDescriptorRanges = dr;
+ rp->DescriptorTable.NumDescriptorRanges = 1;
+ rp->ShaderVisibility = visibility;
+}
+
+static bool BuildRootSignature(ID3D12Device* device, ID3D12RootSignature** sig_ptr,
+ const D3D12_ROOT_PARAMETER* params, u32 num_params)
+{
+ D3D12_ROOT_SIGNATURE_DESC desc = {};
+ desc.pParameters = params;
+ desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |
+ D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
+ D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS;
+ desc.NumParameters = num_params;
+
+ ComPtr root_signature_blob;
+ ComPtr root_signature_error_blob;
+
+ HRESULT hr = d3d12_serialize_root_signature(&desc, D3D_ROOT_SIGNATURE_VERSION_1,
+ &root_signature_blob, &root_signature_error_blob);
+ if (FAILED(hr))
+ {
+ PanicAlert("Failed to serialize root signature: %s",
+ static_cast(root_signature_error_blob->GetBufferPointer()));
+ return false;
+ }
+
+ hr = device->CreateRootSignature(0, root_signature_blob->GetBufferPointer(),
+ root_signature_blob->GetBufferSize(), IID_PPV_ARGS(sig_ptr));
+ CHECK(SUCCEEDED(hr), "Create root signature");
+ return true;
+}
+
+bool DXContext::CreateRootSignatures()
+{
+ std::array params;
+ std::array ranges;
+
+ // GX:
+ // - 3 constant buffers (bindings 0-2), 0/1 visible in PS, 1 visible in VS, 2 visible in GS.
+ // - 8 textures (visible in PS).
+ // - 8 samplers (visible in PS).
+ // - 1 UAV (visible in PS).
+ SetRootParamCBV(¶ms[DESCRIPTOR_TABLE_PS_CBV], 0, D3D12_SHADER_VISIBILITY_PIXEL);
+ SetRootParamCBV(¶ms[DESCRIPTOR_TABLE_VS_CBV], 0, D3D12_SHADER_VISIBILITY_VERTEX);
+ SetRootParamCBV(¶ms[DESCRIPTOR_TABLE_GS_CBV], 0, D3D12_SHADER_VISIBILITY_GEOMETRY);
+ SetRootParamTable(¶ms[DESCRIPTOR_TABLE_PS_SRV], &ranges[DESCRIPTOR_TABLE_PS_SRV],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 8, D3D12_SHADER_VISIBILITY_PIXEL);
+ SetRootParamTable(¶ms[DESCRIPTOR_TABLE_PS_SAMPLERS], &ranges[DESCRIPTOR_TABLE_PS_SAMPLERS],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, 8, D3D12_SHADER_VISIBILITY_PIXEL);
+ SetRootParamTable(¶ms[DESCRIPTOR_TABLE_PS_UAV], &ranges[DESCRIPTOR_TABLE_PS_UAV],
+ D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 2, 1, D3D12_SHADER_VISIBILITY_PIXEL);
+ if (!BuildRootSignature(m_device.Get(), &m_gx_root_signature, params.data(), 6))
+ return false;
+
+ // Utility:
+ // - 1 constant buffer (binding 0, visible in VS/PS).
+ // - 8 textures (visible in PS).
+ // - 8 samplers (visible in PS).
+ SetRootParamCBV(¶ms[DESCRIPTOR_TABLE_PS_CBV], 0, D3D12_SHADER_VISIBILITY_ALL);
+ SetRootParamTable(¶ms[DESCRIPTOR_TABLE_PS_SRV], &ranges[DESCRIPTOR_TABLE_PS_SRV],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 8, D3D12_SHADER_VISIBILITY_PIXEL);
+ SetRootParamTable(¶ms[DESCRIPTOR_TABLE_PS_SAMPLERS], &ranges[DESCRIPTOR_TABLE_PS_SAMPLERS],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, 8, D3D12_SHADER_VISIBILITY_PIXEL);
+ if (!BuildRootSignature(m_device.Get(), &m_utility_root_signature, params.data(), 3))
+ return false;
+
+ // Compute:
+ // - 1 constant buffer (binding 0).
+ // - 8 textures.
+ // - 8 samplers.
+ // - 1 UAV.
+ SetRootParamCBV(¶ms[DESCRIPTOR_TABLE_PS_CBV], 0, D3D12_SHADER_VISIBILITY_ALL);
+ SetRootParamTable(¶ms[DESCRIPTOR_TABLE_PS_SRV], &ranges[DESCRIPTOR_TABLE_PS_SRV],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 8, D3D12_SHADER_VISIBILITY_ALL);
+ SetRootParamTable(¶ms[DESCRIPTOR_TABLE_PS_SAMPLERS], &ranges[DESCRIPTOR_TABLE_PS_SAMPLERS],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, 8, D3D12_SHADER_VISIBILITY_ALL);
+ SetRootParamTable(¶ms[DESCRIPTOR_TABLE_PS_UAV], &ranges[DESCRIPTOR_TABLE_PS_UAV],
+ D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 0, 1, D3D12_SHADER_VISIBILITY_ALL);
+ if (!BuildRootSignature(m_device.Get(), &m_compute_root_signature, params.data(), 4))
+ return false;
+
+ return true;
+}
+
+bool DXContext::CreateTextureUploadBuffer()
+{
+ m_texture_upload_buffer = StreamBuffer::Create(TEXTURE_UPLOAD_BUFFER_SIZE);
+ if (!m_texture_upload_buffer)
+ {
+ PanicAlert("Failed to create texture upload buffer");
+ return false;
+ }
+
+ return true;
+}
+
+bool DXContext::CreateCommandLists()
+{
+ for (u32 i = 0; i < NUM_COMMAND_LISTS; i++)
+ {
+ CommandListResources& res = m_command_lists[i];
+ HRESULT hr = m_device->CreateCommandAllocator(
+ D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(res.command_allocator.GetAddressOf()));
+ CHECK(SUCCEEDED(hr), "Create command allocator");
+ if (FAILED(hr))
+ return false;
+
+ hr = m_device->CreateCommandList(1, D3D12_COMMAND_LIST_TYPE_DIRECT, res.command_allocator.Get(),
+ nullptr, IID_PPV_ARGS(res.command_list.GetAddressOf()));
+ CHECK(SUCCEEDED(hr), "Create command list");
+ if (FAILED(hr))
+ return false;
+
+ // Close the command list, since the first thing we do is reset them.
+ hr = res.command_list->Close();
+ CHECK(SUCCEEDED(hr), "Closing new command list failed");
+ if (FAILED(hr))
+ return false;
+
+ res.ready_fence_value = m_last_fence_value;
+ }
+
+ MoveToNextCommandList();
+ return true;
+}
+
+void DXContext::MoveToNextCommandList()
+{
+ m_current_command_list = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
+ m_next_fence_value++;
+
+ // We may have to wait if this command list hasn't finished on the GPU.
+ CommandListResources& res = m_command_lists[m_current_command_list];
+ WaitForFence(res.ready_fence_value);
+
+ // Begin command list.
+ res.command_allocator->Reset();
+ res.command_list->Reset(res.command_allocator.Get(), nullptr);
+ res.ready_fence_value = m_next_fence_value;
+}
+
+void DXContext::ExecuteCommandList(bool wait_for_completion)
+{
+ CommandListResources& res = m_command_lists[m_current_command_list];
+
+ // We only need to track fences for the CBV/SRV/UAV heap, since the others don't use temporaries.
+ m_descriptor_heap_manager->FenceQueued(m_next_fence_value);
+
+ // Close and queue command list.
+ HRESULT hr = res.command_list->Close();
+ CHECK(SUCCEEDED(hr), "Close command list");
+ ID3D12CommandList* const execute_lists[] = {res.command_list.Get()};
+ m_command_queue->ExecuteCommandLists(static_cast(ArraySize(execute_lists)), execute_lists);
+
+ // Update fence when GPU has completed.
+ hr = m_command_queue->Signal(m_fence.Get(), m_next_fence_value);
+ CHECK(SUCCEEDED(hr), "Signal fence");
+
+ MoveToNextCommandList();
+ if (wait_for_completion)
+ WaitForFence(res.ready_fence_value);
+}
+
+void DXContext::DeferResourceDestruction(ID3D12Resource* resource)
+{
+ resource->AddRef();
+ m_command_lists[m_current_command_list].pending_resources.push_back(resource);
+}
+
+void DXContext::DeferDescriptorDestruction(DescriptorHeapManager* manager, u32 index)
+{
+ m_command_lists[m_current_command_list].pending_descriptors.emplace_back(manager, index);
+}
+
+void DXContext::DestroyPendingResources(CommandListResources& cmdlist)
+{
+ for (const auto& dd : cmdlist.pending_descriptors)
+ dd.first->Free(dd.second);
+ cmdlist.pending_descriptors.clear();
+
+ for (ID3D12Resource* res : cmdlist.pending_resources)
+ res->Release();
+ cmdlist.pending_resources.clear();
+}
+
+void DXContext::WaitForFence(u64 fence)
+{
+ if (m_last_fence_value >= fence)
+ return;
+
+ // Try non-blocking check.
+ m_last_fence_value = m_fence->GetCompletedValue();
+ if (m_last_fence_value < fence)
+ {
+ // Fall back to event.
+ HRESULT hr = m_fence->SetEventOnCompletion(fence, m_fence_event);
+ CHECK(SUCCEEDED(hr), "Set fence event on completion");
+ WaitForSingleObject(m_fence_event, INFINITE);
+ m_last_fence_value = m_fence->GetCompletedValue();
+ }
+
+ // Release resources for as many command lists which have completed.
+ u32 index = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
+ for (u32 i = 0; i < NUM_COMMAND_LISTS; i++)
+ {
+ CommandListResources& res = m_command_lists[index];
+ if (m_last_fence_value < res.ready_fence_value)
+ break;
+
+ DestroyPendingResources(res);
+ index = (index + 1) % NUM_COMMAND_LISTS;
+ }
+
+ // Notify watchers.
+ ExecuteFenceSignaledCallbacks(m_last_fence_value);
+}
+
+void DXContext::AddFenceSignaledCallback(const void* key, FenceSignaledCallback callback)
+{
+ DEBUG_ASSERT(m_fence_callbacks.find(key) == m_fence_callbacks.end());
+ m_fence_callbacks.emplace(key, callback);
+}
+
+void DXContext::RemoveFenceSignaledCallback(const void* key)
+{
+ auto iter = m_fence_callbacks.find(key);
+ if (iter != m_fence_callbacks.end())
+ m_fence_callbacks.erase(iter);
+}
+
+void DXContext::ExecuteFenceSignaledCallbacks(u64 value)
+{
+ m_descriptor_heap_manager->FenceSignaled(value);
+
+ // A fence callback can remove itself.
+ for (auto iter = m_fence_callbacks.begin(); iter != m_fence_callbacks.end();)
+ (iter++)->second(value);
+}
+} // namespace DX12
diff --git a/Source/Core/VideoBackends/D3D12/DXContext.h b/Source/Core/VideoBackends/D3D12/DXContext.h
new file mode 100644
index 000000000000..23225888eeb5
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/DXContext.h
@@ -0,0 +1,165 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+#include "Common/CommonTypes.h"
+#include "D3DBase.h"
+
+#include
+#include
+#include