diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index 4468f7c57d..645bb876a7 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -336,6 +336,7 @@ if(WIN32)
)
target_link_libraries(core PUBLIC
videod3d
+ videod3d12
setupapi.lib
iphlpapi.lib
)
diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj
index e2fefa340c..18f253880e 100644
--- a/Source/Core/DolphinQt/DolphinQt.vcxproj
+++ b/Source/Core/DolphinQt/DolphinQt.vcxproj
@@ -475,6 +475,9 @@
{4c3b2264-ea73-4a7b-9cfe-65b0fd635ebb}
+
+ {570215b7-e32f-4438-95ae-c8d955f9fca3}
+
diff --git a/Source/Core/VideoBackends/CMakeLists.txt b/Source/Core/VideoBackends/CMakeLists.txt
index b53d85e10a..51ea342826 100644
--- a/Source/Core/VideoBackends/CMakeLists.txt
+++ b/Source/Core/VideoBackends/CMakeLists.txt
@@ -6,5 +6,6 @@ add_subdirectory(Vulkan)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
add_subdirectory(D3DCommon)
add_subdirectory(D3D)
+ add_subdirectory(D3D12)
endif()
diff --git a/Source/Core/VideoBackends/D3D12/BoundingBox.cpp b/Source/Core/VideoBackends/D3D12/BoundingBox.cpp
new file mode 100644
index 0000000000..c6b5db3b39
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/BoundingBox.cpp
@@ -0,0 +1,183 @@
+// 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"
+
+namespace DX12
+{
+BoundingBox::BoundingBox() = default;
+
+BoundingBox::~BoundingBox()
+{
+ if (m_gpu_descriptor)
+ g_dx_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};
+ static constexpr D3D12_HEAP_PROPERTIES cpu_heap_properties = {D3D12_HEAP_TYPE_READBACK};
+ 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_dx_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_dx_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_dx_context->GetDevice()->CreateUnorderedAccessView(m_gpu_buffer.Get(), nullptr, &uav_desc,
+ m_gpu_descriptor.cpu_handle);
+
+ buffer_desc.Flags = D3D12_RESOURCE_FLAG_NONE;
+ hr = g_dx_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;
+
+ if (!m_upload_buffer.AllocateBuffer(STREAM_BUFFER_SIZE))
+ 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.
+ ResourceBarrier(g_dx_context->GetCommandList(), m_gpu_buffer.Get(),
+ D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
+ g_dx_context->GetCommandList()->CopyBufferRegion(m_readback_buffer.Get(), 0, m_gpu_buffer.Get(),
+ 0, BUFFER_SIZE);
+ ResourceBarrier(g_dx_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()
+{
+ bool in_copy_state = false;
+ for (u32 start = 0; start < NUM_VALUES;)
+ {
+ if (!m_dirty[start])
+ {
+ start++;
+ continue;
+ }
+
+ u32 end = start + 1;
+ m_dirty[start] = false;
+ for (; end < NUM_VALUES; end++)
+ {
+ if (!m_dirty[end])
+ break;
+
+ m_dirty[end] = false;
+ }
+
+ const u32 copy_size = (end - start) * sizeof(ValueType);
+ if (!m_upload_buffer.ReserveMemory(copy_size, sizeof(ValueType)))
+ {
+ WARN_LOG(VIDEO, "Executing command list while waiting for space in bbox stream buffer");
+ Renderer::GetInstance()->ExecuteCommandList(false);
+ if (!m_upload_buffer.ReserveMemory(copy_size, sizeof(ValueType)))
+ {
+ PanicAlert("Failed to allocate bbox stream buffer space");
+ return;
+ }
+ }
+
+ const u32 upload_buffer_offset = m_upload_buffer.GetCurrentOffset();
+ std::memcpy(m_upload_buffer.GetCurrentHostPointer(), &m_values[start], copy_size);
+ m_upload_buffer.CommitMemory(copy_size);
+
+ if (!in_copy_state)
+ {
+ ResourceBarrier(g_dx_context->GetCommandList(), m_gpu_buffer.Get(),
+ D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_DEST);
+ in_copy_state = true;
+ }
+
+ g_dx_context->GetCommandList()->CopyBufferRegion(m_gpu_buffer.Get(), start * sizeof(ValueType),
+ m_upload_buffer.GetBuffer(),
+ upload_buffer_offset, copy_size);
+ start = end;
+ }
+
+ if (in_copy_state)
+ {
+ ResourceBarrier(g_dx_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 0000000000..45cb979ca8
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/BoundingBox.h
@@ -0,0 +1,49 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+#include
+#include "VideoBackends/D3D12/Common.h"
+#include "VideoBackends/D3D12/DescriptorHeapManager.h"
+#include "VideoBackends/D3D12/StreamBuffer.h"
+
+namespace DX12
+{
+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:
+ using ValueType = s32;
+ static const u32 NUM_VALUES = 4;
+ static const u32 BUFFER_SIZE = sizeof(ValueType) * 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: GPU for read/write, CPU for reading back, and CPU for staging changes.
+ ComPtr m_gpu_buffer;
+ ComPtr m_readback_buffer;
+ StreamBuffer m_upload_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/CMakeLists.txt b/Source/Core/VideoBackends/D3D12/CMakeLists.txt
new file mode 100644
index 0000000000..08eec49005
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/CMakeLists.txt
@@ -0,0 +1,37 @@
+add_library(videod3d12
+ BoundingBox.cpp
+ BoundingBox.h
+ DescriptorAllocator.cpp
+ DescriptorAllocator.h
+ DescriptorHeapManager.cpp
+ DescriptorHeapManager.h
+ DXContext.cpp
+ DXContext.h
+ DXPipeline.cpp
+ DXPipeline.h
+ DXShader.cpp
+ DXShader.h
+ DXTexture.cpp
+ DXTexture.h
+ DXVertexFormat.cpp
+ DXVertexFormat.h
+ PerfQuery.cpp
+ PerfQuery.h
+ Renderer.cpp
+ Renderer.h
+ StreamBuffer.cpp
+ StreamBuffer.h
+ SwapChain.cpp
+ SwapChain.h
+ VertexManager.cpp
+ VertexManager.h
+ VideoBackend.cpp
+ VideoBackend.h
+)
+
+target_link_libraries(videod3d12
+PUBLIC
+ common
+ videocommon
+ videod3dcommon
+)
diff --git a/Source/Core/VideoBackends/D3D12/Common.h b/Source/Core/VideoBackends/D3D12/Common.h
new file mode 100644
index 0000000000..47d0307350
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/Common.h
@@ -0,0 +1,32 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+#pragma once
+
+#include
+#include
+
+#include "Common/MsgHandler.h"
+#include "VideoBackends/D3DCommon/Common.h"
+
+#define CHECK(cond, Message, ...) \
+ if (!(cond)) \
+ { \
+ PanicAlert(__FUNCTION__ " failed in %s at line %d: " Message, __FILE__, __LINE__, \
+ __VA_ARGS__); \
+ }
+
+namespace DX12
+{
+using Microsoft::WRL::ComPtr;
+
+static 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 DX12
diff --git a/Source/Core/VideoBackends/D3D12/D3D12.vcxproj b/Source/Core/VideoBackends/D3D12/D3D12.vcxproj
new file mode 100644
index 0000000000..e382597b84
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/D3D12.vcxproj
@@ -0,0 +1,94 @@
+
+
+
+
+ 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}
+
+
+ {dea96cf2-f237-4a1a-b32f-c916769efb50}
+
+
+
+
+
+
\ 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 0000000000..24feadc6ff
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/D3D12.vcxproj.filters
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/Core/VideoBackends/D3D12/DXContext.cpp b/Source/Core/VideoBackends/D3D12/DXContext.cpp
new file mode 100644
index 0000000000..6c3611ed00
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/DXContext.cpp
@@ -0,0 +1,548 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+#include
+#include
+#include
+
+#include "Common/Assert.h"
+#include "Common/DynamicLibrary.h"
+#include "Common/StringUtil.h"
+#include "VideoBackends/D3D12/Common.h"
+#include "VideoBackends/D3D12/DXContext.h"
+#include "VideoBackends/D3D12/DescriptorHeapManager.h"
+#include "VideoBackends/D3D12/StreamBuffer.h"
+#include "VideoCommon/VideoConfig.h"
+
+namespace DX12
+{
+std::unique_ptr g_dx_context;
+
+// Private D3D12 state
+static Common::DynamicLibrary s_d3d12_library;
+static PFN_D3D12_CREATE_DEVICE s_d3d12_create_device;
+static PFN_D3D12_GET_DEBUG_INTERFACE s_d3d12_get_debug_interface;
+static PFN_D3D12_SERIALIZE_ROOT_SIGNATURE s_d3d12_serialize_root_signature;
+
+DXContext::DXContext() = default;
+
+DXContext::~DXContext()
+{
+ if (m_fence_event)
+ CloseHandle(m_fence_event);
+}
+
+std::vector DXContext::GetAAModes(u32 adapter_index)
+{
+ // Use a temporary device if we aren't booting.
+ Common::DynamicLibrary temp_lib;
+ ComPtr temp_device = g_dx_context ? g_dx_context->m_device : nullptr;
+ if (!temp_device)
+ {
+ ComPtr temp_dxgi_factory = D3DCommon::CreateDXGIFactory(false);
+ if (!temp_dxgi_factory)
+ return {};
+
+ ComPtr adapter;
+ temp_dxgi_factory->EnumAdapters(adapter_index, &adapter);
+
+ PFN_D3D12_CREATE_DEVICE d3d12_create_device;
+ if (!temp_lib.Open("d3d12.dll") ||
+ !temp_lib.GetSymbol("D3D12CreateDevice", &d3d12_create_device))
+ {
+ return {};
+ }
+
+ HRESULT hr = d3d12_create_device(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&temp_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;
+
+ temp_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);
+ }
+
+ return aa_modes;
+}
+
+bool DXContext::SupportsTextureFormat(DXGI_FORMAT format)
+{
+ constexpr u32 required = D3D12_FORMAT_SUPPORT1_TEXTURE2D | D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE;
+
+ D3D12_FEATURE_DATA_FORMAT_SUPPORT support = {format};
+ return SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &support,
+ sizeof(support))) &&
+ (support.Support1 & required) == required;
+}
+
+bool DXContext::Create(u32 adapter_index, bool enable_debug_layer)
+{
+ ASSERT(!g_dx_context);
+ if (!s_d3d12_library.Open("d3d12.dll") ||
+ !s_d3d12_library.GetSymbol("D3D12CreateDevice", &s_d3d12_create_device) ||
+ !s_d3d12_library.GetSymbol("D3D12GetDebugInterface", &s_d3d12_get_debug_interface) ||
+ !s_d3d12_library.GetSymbol("D3D12SerializeRootSignature", &s_d3d12_serialize_root_signature))
+ {
+ PanicAlertT("d3d12.dll could not be loaded.");
+ s_d3d12_library.Close();
+ return false;
+ }
+
+ if (!D3DCommon::LoadLibraries())
+ {
+ s_d3d12_library.Close();
+ return false;
+ }
+
+ g_dx_context.reset(new DXContext());
+ if (!g_dx_context->CreateDXGIFactory(enable_debug_layer) ||
+ !g_dx_context->CreateDevice(adapter_index, enable_debug_layer) ||
+ !g_dx_context->CreateCommandQueue() || !g_dx_context->CreateFence())
+ {
+ Destroy();
+ return false;
+ }
+
+ return true;
+}
+
+bool DXContext::CreateGlobalResources()
+{
+ return g_dx_context->CreateDescriptorHeaps() && g_dx_context->CreateRootSignatures() &&
+ g_dx_context->CreateTextureUploadBuffer() && g_dx_context->CreateCommandLists();
+}
+
+void DXContext::Destroy()
+{
+ if (g_dx_context)
+ g_dx_context.reset();
+
+ s_d3d12_serialize_root_signature = nullptr;
+ s_d3d12_get_debug_interface = nullptr;
+ s_d3d12_create_device = nullptr;
+ s_d3d12_library.Close();
+ D3DCommon::UnloadLibraries();
+}
+
+bool DXContext::CreateDXGIFactory(bool enable_debug_layer)
+{
+ m_dxgi_factory = D3DCommon::CreateDXGIFactory(enable_debug_layer);
+ return m_dxgi_factory != nullptr;
+}
+
+bool DXContext::CreateDevice(u32 adapter_index, bool enable_debug_layer)
+{
+ ComPtr adapter;
+ HRESULT hr = m_dxgi_factory->EnumAdapters(adapter_index, &adapter);
+ if (FAILED(hr))
+ {
+ ERROR_LOG(VIDEO, "Adapter %u 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 = s_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 = s_d3d12_create_device(adapter.Get(), 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};
+ 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_completed_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_SRVS = 16384;
+ static constexpr size_t MAX_RTVS = 8192;
+ static constexpr size_t MAX_DSVS = 128;
+ static constexpr size_t MAX_SAMPLERS = 16384;
+
+ if (!m_descriptor_heap_manager.Create(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
+ MAX_SRVS) ||
+ !m_rtv_heap_manager.Create(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, MAX_RTVS) ||
+ !m_dsv_heap_manager.Create(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_DSV, MAX_DSVS) ||
+ !m_sampler_heap_manager.Create(m_device.Get(), MAX_SAMPLERS))
+ {
+ return false;
+ }
+
+ m_gpu_descriptor_heaps[1] = m_sampler_heap_manager.GetDescriptorHeap();
+
+ // Allocate null SRV descriptor for unbound textures.
+ constexpr D3D12_SHADER_RESOURCE_VIEW_DESC null_srv_desc = {
+ DXGI_FORMAT_R8G8B8A8_UNORM, D3D12_SRV_DIMENSION_TEXTURE2D,
+ D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING};
+
+ if (!m_descriptor_heap_manager.Allocate(&m_null_srv_descriptor))
+ {
+ PanicAlert("Failed to allocate null descriptor");
+ return false;
+ }
+
+ m_device->CreateShaderResourceView(nullptr, &null_srv_desc, m_null_srv_descriptor.cpu_handle);
+ 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 = s_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()
+{
+ return CreateGXRootSignature() && CreateUtilityRootSignature() && CreateComputeRootSignature();
+}
+
+bool DXContext::CreateGXRootSignature()
+{
+ // 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).
+
+ std::array params;
+ std::array ranges;
+ u32 param_count = 0;
+ SetRootParamCBV(¶ms[param_count], 0, D3D12_SHADER_VISIBILITY_PIXEL);
+ param_count++;
+ SetRootParamTable(¶ms[param_count], &ranges[param_count], D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0,
+ 8, D3D12_SHADER_VISIBILITY_PIXEL);
+ param_count++;
+ SetRootParamTable(¶ms[param_count], &ranges[param_count], D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
+ 0, 8, D3D12_SHADER_VISIBILITY_PIXEL);
+ param_count++;
+ SetRootParamCBV(¶ms[param_count], 0, D3D12_SHADER_VISIBILITY_VERTEX);
+ param_count++;
+ SetRootParamCBV(¶ms[param_count], 0, D3D12_SHADER_VISIBILITY_GEOMETRY);
+ param_count++;
+
+ // Since these must be contiguous, pixel lighting goes to bbox if not enabled.
+ if (g_ActiveConfig.bBBoxEnable)
+ {
+ SetRootParamTable(¶ms[param_count], &ranges[param_count], D3D12_DESCRIPTOR_RANGE_TYPE_UAV,
+ 2, 1, D3D12_SHADER_VISIBILITY_PIXEL);
+ param_count++;
+ }
+ if (g_ActiveConfig.bEnablePixelLighting)
+ {
+ SetRootParamCBV(¶ms[param_count], 1, D3D12_SHADER_VISIBILITY_PIXEL);
+ param_count++;
+ }
+
+ return BuildRootSignature(m_device.Get(), &m_gx_root_signature, params.data(), param_count);
+}
+
+bool DXContext::CreateUtilityRootSignature()
+{
+ // Utility:
+ // - 1 constant buffer (binding 0, visible in VS/PS).
+ // - 8 textures (visible in PS).
+ // - 8 samplers (visible in PS).
+
+ std::array params;
+ std::array ranges;
+ SetRootParamCBV(¶ms[ROOT_PARAMETER_PS_CBV], 0, D3D12_SHADER_VISIBILITY_ALL);
+ SetRootParamTable(¶ms[ROOT_PARAMETER_PS_SRV], &ranges[ROOT_PARAMETER_PS_SRV],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 8, D3D12_SHADER_VISIBILITY_PIXEL);
+ SetRootParamTable(¶ms[ROOT_PARAMETER_PS_SAMPLERS], &ranges[ROOT_PARAMETER_PS_SAMPLERS],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, 8, D3D12_SHADER_VISIBILITY_PIXEL);
+ return BuildRootSignature(m_device.Get(), &m_utility_root_signature, params.data(), 3);
+}
+
+bool DXContext::CreateComputeRootSignature()
+{
+ // Compute:
+ // - 1 constant buffer (binding 0).
+ // - 8 textures.
+ // - 8 samplers.
+ // - 1 UAV.
+
+ std::array params;
+ std::array ranges;
+ SetRootParamCBV(¶ms[CS_ROOT_PARAMETER_CBV], 0, D3D12_SHADER_VISIBILITY_ALL);
+ SetRootParamTable(¶ms[CS_ROOT_PARAMETER_SRV], &ranges[CS_ROOT_PARAMETER_CBV],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 8, D3D12_SHADER_VISIBILITY_ALL);
+ SetRootParamTable(¶ms[CS_ROOT_PARAMETER_SAMPLERS], &ranges[CS_ROOT_PARAMETER_SAMPLERS],
+ D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, 8, D3D12_SHADER_VISIBILITY_ALL);
+ SetRootParamTable(¶ms[CS_ROOT_PARAMETER_UAV], &ranges[CS_ROOT_PARAMETER_UAV],
+ D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 0, 1, D3D12_SHADER_VISIBILITY_ALL);
+ return BuildRootSignature(m_device.Get(), &m_compute_root_signature, params.data(), 4);
+}
+
+bool DXContext::CreateTextureUploadBuffer()
+{
+ if (!m_texture_upload_buffer.AllocateBuffer(TEXTURE_UPLOAD_BUFFER_SIZE))
+ {
+ PanicAlert("Failed to create texture upload buffer");
+ return false;
+ }
+
+ return true;
+}
+
+bool DXContext::CreateCommandLists()
+{
+ static constexpr size_t MAX_DRAWS_PER_FRAME = 8192;
+ static constexpr size_t TEMPORARY_SLOTS = MAX_DRAWS_PER_FRAME * 8;
+
+ 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()));
+ if (FAILED(hr))
+ {
+ PanicAlert("Failed to create command list.");
+ 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;
+
+ if (!res.descriptor_allocator.Create(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
+ TEMPORARY_SLOTS) ||
+ !res.sampler_allocator.Create(m_device.Get()))
+ {
+ return false;
+ }
+ }
+
+ MoveToNextCommandList();
+ return true;
+}
+
+void DXContext::MoveToNextCommandList()
+{
+ m_current_command_list = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
+ m_current_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.descriptor_allocator.Reset();
+ if (res.sampler_allocator.ShouldReset())
+ res.sampler_allocator.Reset();
+ m_gpu_descriptor_heaps[0] = res.descriptor_allocator.GetDescriptorHeap();
+ m_gpu_descriptor_heaps[1] = res.sampler_allocator.GetDescriptorHeap();
+ res.ready_fence_value = m_current_fence_value;
+}
+
+void DXContext::ExecuteCommandList(bool wait_for_completion)
+{
+ CommandListResources& res = m_command_lists[m_current_command_list];
+
+ // 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_current_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::ResetSamplerAllocators()
+{
+ for (CommandListResources& res : m_command_lists)
+ res.sampler_allocator.Reset();
+}
+
+void DXContext::RecreateGXRootSignature()
+{
+ m_gx_root_signature.Reset();
+ if (!CreateGXRootSignature())
+ PanicAlert("Failed to re-create GX root signature.");
+}
+
+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_completed_fence_value >= fence)
+ return;
+
+ // Try non-blocking check.
+ m_completed_fence_value = m_fence->GetCompletedValue();
+ if (m_completed_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_completed_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_completed_fence_value < res.ready_fence_value)
+ break;
+
+ DestroyPendingResources(res);
+ index = (index + 1) % NUM_COMMAND_LISTS;
+ }
+}
+} // namespace DX12
diff --git a/Source/Core/VideoBackends/D3D12/DXContext.h b/Source/Core/VideoBackends/D3D12/DXContext.h
new file mode 100644
index 0000000000..4f1993d2b5
--- /dev/null
+++ b/Source/Core/VideoBackends/D3D12/DXContext.h
@@ -0,0 +1,191 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+#include "Common/CommonTypes.h"
+#include "VideoBackends/D3D12/Common.h"
+#include "VideoBackends/D3D12/DescriptorAllocator.h"
+#include "VideoBackends/D3D12/DescriptorHeapManager.h"
+#include "VideoBackends/D3D12/StreamBuffer.h"
+
+#include
+#include
+#include