diff --git a/common/D3D12/Texture.cpp b/common/D3D12/Texture.cpp
index 93a78259dc..83d8bbf146 100644
--- a/common/D3D12/Texture.cpp
+++ b/common/D3D12/Texture.cpp
@@ -276,7 +276,7 @@ void Texture::TransitionToState(ID3D12GraphicsCommandList* cmdlist, D3D12_RESOUR
}
void Texture::TransitionSubresourceToState(ID3D12GraphicsCommandList* cmdlist, u32 level,
- D3D12_RESOURCE_STATES before_state, D3D12_RESOURCE_STATES after_state)
+ D3D12_RESOURCE_STATES before_state, D3D12_RESOURCE_STATES after_state) const
{
const D3D12_RESOURCE_BARRIER barrier = {D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
D3D12_RESOURCE_BARRIER_FLAG_NONE,
diff --git a/common/D3D12/Texture.h b/common/D3D12/Texture.h
index af9c88aad1..9d0f05eb36 100644
--- a/common/D3D12/Texture.h
+++ b/common/D3D12/Texture.h
@@ -68,7 +68,7 @@ namespace D3D12
void TransitionToState(ID3D12GraphicsCommandList* cmdlist, D3D12_RESOURCE_STATES state);
void TransitionSubresourceToState(ID3D12GraphicsCommandList* cmdlist, u32 level,
- D3D12_RESOURCE_STATES before_state, D3D12_RESOURCE_STATES after_state);
+ D3D12_RESOURCE_STATES before_state, D3D12_RESOURCE_STATES after_state) const;
Texture& operator=(const Texture&) = delete;
Texture& operator=(Texture&& texture);
diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt
index 50001d7637..db15d8775a 100644
--- a/pcsx2/CMakeLists.txt
+++ b/pcsx2/CMakeLists.txt
@@ -1022,9 +1022,11 @@ endif()
if(WIN32)
list(APPEND pcsx2FrontendSources
Frontend/D3D11HostDisplay.cpp
+ Frontend/D3D12HostDisplay.cpp
)
list(APPEND pcsx2FrontendHeaders
Frontend/D3D11HostDisplay.h
+ Frontend/D3D12HostDisplay.h
)
elseif(APPLE)
list(APPEND pcsx2GSSources
@@ -1621,6 +1623,7 @@ if(WIN32)
baseclasses
pthreads4w
WIL::WIL
+ D3D12MemAlloc
setupapi.lib
ws2_32.lib
shlwapi.lib
diff --git a/pcsx2/Frontend/D3D12HostDisplay.cpp b/pcsx2/Frontend/D3D12HostDisplay.cpp
new file mode 100644
index 0000000000..ecfb86ac4c
--- /dev/null
+++ b/pcsx2/Frontend/D3D12HostDisplay.cpp
@@ -0,0 +1,759 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "Frontend/D3D12HostDisplay.h"
+#include "common/Assertions.h"
+#include "common/Console.h"
+#include "common/D3D12/Context.h"
+#include "common/D3D12/Util.h"
+#include "common/StringUtil.h"
+
+#include "imgui.h"
+#include "backends/imgui_impl_dx12.h"
+#include
+#include
+
+class D3D12HostDisplayTexture : public HostDisplayTexture
+{
+public:
+ D3D12HostDisplayTexture(D3D12::Texture texture)
+ : m_texture(std::move(texture))
+ {
+ }
+ ~D3D12HostDisplayTexture() override = default;
+
+ void* GetHandle() const override { return const_cast(&m_texture); }
+ u32 GetWidth() const override { return m_texture.GetWidth(); }
+ u32 GetHeight() const override { return m_texture.GetHeight(); }
+
+ const D3D12::Texture& GetTexture() const { return m_texture; }
+ D3D12::Texture& GetTexture() { return m_texture; }
+
+private:
+ D3D12::Texture m_texture;
+};
+
+D3D12HostDisplay::D3D12HostDisplay() = default;
+
+D3D12HostDisplay::~D3D12HostDisplay()
+{
+ pxAssertMsg(!g_d3d12_context, "Context should have been destroyed by now");
+ pxAssertMsg(!m_swap_chain, "Swap chain should have been destroyed by now");
+}
+
+HostDisplay::RenderAPI D3D12HostDisplay::GetRenderAPI() const
+{
+ return HostDisplay::RenderAPI::D3D12;
+}
+
+void* D3D12HostDisplay::GetRenderDevice() const
+{
+ return g_d3d12_context->GetDevice();
+}
+
+void* D3D12HostDisplay::GetRenderContext() const
+{
+ return g_d3d12_context.get();
+}
+
+void* D3D12HostDisplay::GetRenderSurface() const
+{
+ return m_swap_chain.get();
+}
+
+bool D3D12HostDisplay::HasRenderDevice() const
+{
+ return static_cast(g_d3d12_context);
+}
+
+bool D3D12HostDisplay::HasRenderSurface() const
+{
+ return static_cast(m_swap_chain);
+}
+
+std::unique_ptr D3D12HostDisplay::CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic /* = false */)
+{
+ D3D12::Texture tex;
+ if (!tex.Create(width, height, 1, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN,
+ D3D12_RESOURCE_FLAG_NONE))
+ {
+ return {};
+ }
+
+ if (data && !tex.LoadData(0, 0, 0, width, height, data, data_stride))
+ return {};
+
+ return std::make_unique(std::move(tex));
+}
+
+void D3D12HostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height,
+ const void* texture_data, u32 texture_data_stride)
+{
+ static_cast(texture)->GetTexture().LoadData(0, x, y, width, height, texture_data,
+ texture_data_stride);
+}
+
+bool D3D12HostDisplay::GetHostRefreshRate(float* refresh_rate)
+{
+ if (m_swap_chain && IsFullscreen())
+ {
+ DXGI_SWAP_CHAIN_DESC desc;
+ if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
+ desc.BufferDesc.RefreshRate.Denominator > 0)
+ {
+ DevCon.WriteLn("using fs rr: %u %u", desc.BufferDesc.RefreshRate.Numerator,
+ desc.BufferDesc.RefreshRate.Denominator);
+ *refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) /
+ static_cast(desc.BufferDesc.RefreshRate.Denominator);
+ return true;
+ }
+ }
+
+ return HostDisplay::GetHostRefreshRate(refresh_rate);
+}
+
+void D3D12HostDisplay::SetVSync(VsyncMode mode)
+{
+ m_vsync_mode = mode;
+}
+
+bool D3D12HostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, VsyncMode vsync, bool threaded_presentation, bool debug_device)
+{
+ ComPtr temp_dxgi_factory;
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+ HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(temp_dxgi_factory.put()));
+#else
+ HRESULT hr = CreateDXGIFactory2(0, IID_PPV_ARGS(temp_dxgi_factory.put()));
+#endif
+
+ if (FAILED(hr))
+ {
+ Console.Error("Failed to create DXGI factory: 0x%08X", hr);
+ return false;
+ }
+
+ u32 adapter_index;
+ if (!adapter_name.empty())
+ {
+ AdapterAndModeList adapter_info(GetAdapterAndModeList(temp_dxgi_factory.get()));
+ for (adapter_index = 0; adapter_index < static_cast(adapter_info.adapter_names.size()); adapter_index++)
+ {
+ if (adapter_name == adapter_info.adapter_names[adapter_index])
+ break;
+ }
+ if (adapter_index == static_cast(adapter_info.adapter_names.size()))
+ {
+ Console.Warning("Could not find adapter '%*s', using first (%s)", static_cast(adapter_name.size()),
+ adapter_name.data(), adapter_info.adapter_names[0].c_str());
+ adapter_index = 0;
+ }
+ }
+ else
+ {
+ Console.WriteLn("No adapter selected, using first.");
+ adapter_index = 0;
+ }
+
+ if (!D3D12::Context::Create(temp_dxgi_factory.get(), adapter_index, debug_device))
+ return false;
+
+ if (FAILED(hr))
+ {
+ Console.Error("Failed to create D3D device: 0x%08X", hr);
+ return false;
+ }
+
+ m_dxgi_factory = std::move(temp_dxgi_factory);
+
+ m_allow_tearing_supported = false;
+ ComPtr dxgi_factory5(m_dxgi_factory.try_query());
+ if (dxgi_factory5)
+ {
+ BOOL allow_tearing_supported = false;
+ hr = dxgi_factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
+ sizeof(allow_tearing_supported));
+ if (SUCCEEDED(hr))
+ m_allow_tearing_supported = (allow_tearing_supported == TRUE);
+ }
+
+ m_window_info = wi;
+ return true;
+}
+
+bool D3D12HostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device)
+{
+ if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr))
+ return false;
+
+ return true;
+}
+
+void D3D12HostDisplay::DestroyRenderDevice()
+{
+ g_d3d12_context->ExecuteCommandList(true);
+
+ DestroyRenderSurface();
+ if (g_d3d12_context)
+ g_d3d12_context->Destroy();
+}
+
+bool D3D12HostDisplay::MakeRenderContextCurrent()
+{
+ return true;
+}
+
+bool D3D12HostDisplay::DoneRenderContextCurrent()
+{
+ return true;
+}
+
+bool D3D12HostDisplay::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
+{
+ HRESULT hr;
+
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+ if (m_window_info.type != WindowInfo::Type::Win32)
+ return false;
+
+ const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle);
+ RECT client_rc{};
+ GetClientRect(window_hwnd, &client_rc);
+ const u32 width = static_cast(client_rc.right - client_rc.left);
+ const u32 height = static_cast(client_rc.bottom - client_rc.top);
+
+ DXGI_SWAP_CHAIN_DESC swap_chain_desc = {};
+ swap_chain_desc.BufferDesc.Width = width;
+ swap_chain_desc.BufferDesc.Height = height;
+ swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ swap_chain_desc.SampleDesc.Count = 1;
+ swap_chain_desc.BufferCount = 3;
+ swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swap_chain_desc.OutputWindow = window_hwnd;
+ swap_chain_desc.Windowed = TRUE;
+ swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+
+ m_using_allow_tearing = (m_allow_tearing_supported && !fullscreen_mode);
+ if (m_using_allow_tearing)
+ swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
+
+ if (fullscreen_mode)
+ {
+ swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
+ swap_chain_desc.Windowed = FALSE;
+ swap_chain_desc.BufferDesc = *fullscreen_mode;
+ }
+
+ DevCon.WriteLn("Creating a %dx%d %s swap chain", swap_chain_desc.BufferDesc.Width, swap_chain_desc.BufferDesc.Height,
+ swap_chain_desc.Windowed ? "windowed" : "full-screen");
+
+ hr =
+ m_dxgi_factory->CreateSwapChain(g_d3d12_context->GetCommandQueue(), &swap_chain_desc, m_swap_chain.put());
+ if (FAILED(hr))
+ {
+ Console.Error("CreateSwapChain failed: 0x%08X", hr);
+ return false;
+ }
+
+ hr = m_dxgi_factory->MakeWindowAssociation(swap_chain_desc.OutputWindow, DXGI_MWA_NO_WINDOW_CHANGES);
+ if (FAILED(hr))
+ Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed");
+#else
+ if (m_window_info.type != WindowInfo::Type::WinRT)
+ return false;
+
+ ComPtr factory2;
+ hr = m_dxgi_factory.As(&factory2);
+ if (FAILED(hr))
+ {
+ Console.Error("Failed to get DXGI factory: %08X", hr);
+ return false;
+ }
+
+ DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
+ swap_chain_desc.Width = m_window_info.surface_width;
+ swap_chain_desc.Height = m_window_info.surface_height;
+ swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ swap_chain_desc.SampleDesc.Count = 1;
+ swap_chain_desc.BufferCount = 3;
+ swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+
+ m_using_allow_tearing = (m_allow_tearing_supported && !fullscreen_mode);
+ if (m_using_allow_tearing)
+ swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
+
+ ComPtr swap_chain1;
+ hr = factory2->CreateSwapChainForCoreWindow(g_d3d12_context->GetCommandQueue(),
+ static_cast(m_window_info.window_handle), &swap_chain_desc,
+ nullptr, swap_chain1.GetAddressOf());
+ if (FAILED(hr))
+ {
+ Console.Error("CreateSwapChainForCoreWindow failed: 0x%08X", hr);
+ return false;
+ }
+
+ m_swap_chain = swap_chain1;
+#endif
+
+ return CreateSwapChainRTV();
+}
+
+bool D3D12HostDisplay::CreateSwapChainRTV()
+{
+ DXGI_SWAP_CHAIN_DESC swap_chain_desc;
+ HRESULT hr = m_swap_chain->GetDesc(&swap_chain_desc);
+ if (FAILED(hr))
+ return false;
+
+ for (u32 i = 0; i < swap_chain_desc.BufferCount; i++)
+ {
+ ComPtr backbuffer;
+ hr = m_swap_chain->GetBuffer(i, IID_PPV_ARGS(backbuffer.put()));
+ if (FAILED(hr))
+ {
+ Console.Error("GetBuffer for RTV failed: 0x%08X", hr);
+ return false;
+ }
+
+ D3D12::Texture tex;
+ if (!tex.Adopt(std::move(backbuffer), DXGI_FORMAT_UNKNOWN, swap_chain_desc.BufferDesc.Format, DXGI_FORMAT_UNKNOWN,
+ D3D12_RESOURCE_STATE_PRESENT))
+ {
+ return false;
+ }
+
+ m_swap_chain_buffers.push_back(std::move(tex));
+ }
+
+ m_window_info.surface_width = swap_chain_desc.BufferDesc.Width;
+ m_window_info.surface_height = swap_chain_desc.BufferDesc.Height;
+ DevCon.WriteLn("Swap chain buffer size: %ux%u", m_window_info.surface_width, m_window_info.surface_height);
+
+ if (m_window_info.type == WindowInfo::Type::Win32)
+ {
+ BOOL fullscreen = FALSE;
+ DXGI_SWAP_CHAIN_DESC desc;
+ if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen &&
+ SUCCEEDED(m_swap_chain->GetDesc(&desc)))
+ {
+ m_window_info.surface_refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) /
+ static_cast(desc.BufferDesc.RefreshRate.Denominator);
+ }
+ else
+ {
+ m_window_info.surface_refresh_rate = 0.0f;
+ }
+ }
+
+ m_current_swap_chain_buffer = 0;
+ return true;
+}
+
+void D3D12HostDisplay::DestroySwapChainRTVs()
+{
+ for (D3D12::Texture& buffer : m_swap_chain_buffers)
+ buffer.Destroy(false);
+ m_swap_chain_buffers.clear();
+ m_current_swap_chain_buffer = 0;
+}
+
+bool D3D12HostDisplay::ChangeRenderWindow(const WindowInfo& new_wi)
+{
+ DestroyRenderSurface();
+
+ m_window_info = new_wi;
+ return CreateSwapChain(nullptr);
+}
+
+void D3D12HostDisplay::DestroyRenderSurface()
+{
+ // For some reason if we don't execute the command list here, the swap chain is in use.. not sure where.
+ g_d3d12_context->ExecuteCommandList(true);
+
+ if (IsFullscreen())
+ SetFullscreen(false, 0, 0, 0.0f);
+
+ DestroySwapChainRTVs();
+ m_swap_chain.reset();
+}
+
+static std::string GetDriverVersionFromLUID(const LUID& luid)
+{
+ std::string ret;
+
+ HKEY hKey;
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\DirectX"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
+ {
+ DWORD max_key_len = 0, adapter_count = 0;
+ if (RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, &adapter_count, &max_key_len,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS)
+ {
+ std::vector current_name(max_key_len + 1);
+ for (DWORD i = 0; i < adapter_count; ++i)
+ {
+ DWORD subKeyLength = static_cast(current_name.size());
+ if (RegEnumKeyEx(hKey, i, current_name.data(), &subKeyLength, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS)
+ {
+ LUID current_luid = {};
+ DWORD current_luid_size = sizeof(uint64_t);
+ if (RegGetValue(hKey, current_name.data(), _T("AdapterLuid"), RRF_RT_QWORD, nullptr, ¤t_luid, ¤t_luid_size) == ERROR_SUCCESS &&
+ current_luid.HighPart == luid.HighPart && current_luid.LowPart == luid.LowPart)
+ {
+ LARGE_INTEGER driver_version = {};
+ DWORD driver_version_size = sizeof(driver_version);
+ if (RegGetValue(hKey, current_name.data(), _T("DriverVersion"), RRF_RT_QWORD, nullptr, &driver_version, &driver_version_size) == ERROR_SUCCESS)
+ {
+ WORD nProduct = HIWORD(driver_version.HighPart);
+ WORD nVersion = LOWORD(driver_version.HighPart);
+ WORD nSubVersion = HIWORD(driver_version.LowPart);
+ WORD nBuild = LOWORD(driver_version.LowPart);
+ ret = StringUtil::StdStringFromFormat("%u.%u.%u.%u", nProduct, nVersion, nSubVersion, nBuild);
+ }
+ }
+ }
+ }
+ }
+
+ RegCloseKey(hKey);
+ }
+
+ return ret;
+}
+
+std::string D3D12HostDisplay::GetDriverInfo() const
+{
+ std::string ret = "Unknown Feature Level";
+
+ static constexpr std::array, 4> feature_level_names = {{
+ {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_0"},
+ {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_1"},
+ {D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"},
+ {D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"},
+ }};
+
+ const D3D_FEATURE_LEVEL fl = g_d3d12_context->GetFeatureLevel();
+ for (size_t i = 0; i < std::size(feature_level_names); i++)
+ {
+ if (fl == std::get<0>(feature_level_names[i]))
+ {
+ ret = std::get<1>(feature_level_names[i]);
+ break;
+ }
+ }
+
+ ret += "\n";
+
+ IDXGIAdapter* adapter = g_d3d12_context->GetAdapter();
+ DXGI_ADAPTER_DESC desc;
+ if (adapter && SUCCEEDED(adapter->GetDesc(&desc)))
+ {
+ ret += StringUtil::StdStringFromFormat("VID: 0x%04X PID: 0x%04X\n", desc.VendorId, desc.DeviceId);
+ ret += StringUtil::WideStringToUTF8String(desc.Description);
+ ret += "\n";
+
+ const std::string driver_version(GetDriverVersionFromLUID(desc.AdapterLuid));
+ if (!driver_version.empty())
+ {
+ ret += "Driver Version: ";
+ ret += driver_version;
+ }
+ }
+
+ return ret;
+}
+
+void D3D12HostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
+{
+ if (!m_swap_chain)
+ return;
+
+ m_window_info.surface_scale = new_window_scale;
+
+ if (m_window_info.surface_width == new_window_width && m_window_info.surface_height == new_window_height)
+ return;
+
+ // For some reason if we don't execute the command list here, the swap chain is in use.. not sure where.
+ g_d3d12_context->ExecuteCommandList(true);
+
+ DestroySwapChainRTVs();
+
+ HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
+ m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
+ if (FAILED(hr))
+ Console.Error("ResizeBuffers() failed: 0x%08X", hr);
+
+ if (!CreateSwapChainRTV())
+ pxFailRel("Failed to recreate swap chain RTV after resize");
+}
+
+bool D3D12HostDisplay::SupportsFullscreen() const
+{
+ return true;
+}
+
+bool D3D12HostDisplay::IsFullscreen()
+{
+ BOOL is_fullscreen = FALSE;
+ return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen);
+}
+
+bool D3D12HostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
+{
+ if (!m_swap_chain)
+ return false;
+
+ BOOL is_fullscreen = FALSE;
+ HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr);
+ if (!fullscreen)
+ {
+ // leaving fullscreen
+ if (is_fullscreen)
+ return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr));
+ else
+ return true;
+ }
+
+ IDXGIOutput* output;
+ if (FAILED(hr = m_swap_chain->GetContainingOutput(&output)))
+ return false;
+
+ DXGI_SWAP_CHAIN_DESC current_desc;
+ hr = m_swap_chain->GetDesc(¤t_desc);
+ if (FAILED(hr))
+ return false;
+
+ DXGI_MODE_DESC new_mode = current_desc.BufferDesc;
+ new_mode.Width = width;
+ new_mode.Height = height;
+ new_mode.RefreshRate.Numerator = static_cast(std::floor(refresh_rate * 1000.0f));
+ new_mode.RefreshRate.Denominator = 1000u;
+
+ DXGI_MODE_DESC closest_mode;
+ if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) ||
+ new_mode.Format != current_desc.BufferDesc.Format)
+ {
+ Console.Error("Failed to find closest matching mode, hr=%08X", hr);
+ return false;
+ }
+
+ if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Width &&
+ new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator &&
+ new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator)
+ {
+ DevCon.WriteLn("Fullscreen mode already set");
+ return true;
+ }
+
+ g_d3d12_context->ExecuteCommandList(true);
+ DestroySwapChainRTVs();
+ m_swap_chain.reset();
+
+ if (!CreateSwapChain(&closest_mode))
+ {
+ Console.Error("Failed to create a fullscreen swap chain");
+ if (!CreateSwapChain(nullptr))
+ pxFailRel("Failed to recreate windowed swap chain");
+
+ return false;
+ }
+
+ return true;
+}
+
+HostDisplay::AdapterAndModeList D3D12HostDisplay::GetAdapterAndModeList()
+{
+ return GetAdapterAndModeList(m_dxgi_factory.get());
+}
+
+bool D3D12HostDisplay::CreateImGuiContext()
+{
+ ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width);
+ ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height);
+
+ if (!m_imgui_descriptor_heap.Create(g_d3d12_context->GetDevice(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1, true))
+ return false;
+
+ if (!m_imgui_descriptor_heap.Allocate(&m_imgui_descriptor_handle))
+ {
+ m_imgui_descriptor_heap.Destroy();
+ return false;
+ }
+
+ if (!ImGui_ImplDX12_Init(g_d3d12_context->GetDevice(), D3D12::Context::NUM_COMMAND_LISTS,
+ DXGI_FORMAT_R8G8B8A8_UNORM, m_imgui_descriptor_heap.GetDescriptorHeap(),
+ m_imgui_descriptor_handle, m_imgui_descriptor_handle))
+ {
+ m_imgui_descriptor_heap.Free(m_imgui_descriptor_handle);
+ m_imgui_descriptor_heap.Destroy();
+ return false;
+ }
+
+ return true;
+}
+
+void D3D12HostDisplay::DestroyImGuiContext()
+{
+ g_d3d12_context->WaitForGPUIdle();
+
+ ImGui_ImplDX12_Shutdown();
+
+ m_imgui_descriptor_heap.Free(&m_imgui_descriptor_handle);
+ m_imgui_descriptor_heap.Destroy();
+}
+
+bool D3D12HostDisplay::UpdateImGuiFontTexture()
+{
+ ImGui_ImplDX12_InvalidateDeviceObjects();
+ return ImGui_ImplDX12_CreateDeviceObjects();
+}
+
+bool D3D12HostDisplay::BeginPresent(bool frame_skip)
+{
+ if (frame_skip || !m_swap_chain)
+ {
+ ImGui::EndFrame();
+ return false;
+ }
+
+ static constexpr std::array clear_color = {};
+ D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
+
+ ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList();
+ swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET);
+ cmdlist->ClearRenderTargetView(swap_chain_buf.GetRTVOrDSVDescriptor(), clear_color.data(), 0, nullptr);
+ cmdlist->OMSetRenderTargets(1, &swap_chain_buf.GetRTVOrDSVDescriptor().cpu_handle, FALSE, nullptr);
+
+ const D3D12_VIEWPORT vp{0.0f, 0.0f, static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height), 0.0f, 1.0f};
+ const D3D12_RECT scissor{0, 0, static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height)};
+ cmdlist->RSSetViewports(1, &vp);
+ cmdlist->RSSetScissorRects(1, &scissor);
+ return true;
+}
+
+void D3D12HostDisplay::EndPresent()
+{
+ ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList();
+ ID3D12DescriptorHeap* heaps[] = {m_imgui_descriptor_heap.GetDescriptorHeap()};
+ cmdlist->SetDescriptorHeaps(std::size(heaps), heaps);
+
+ ImGui::Render();
+ ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), cmdlist);
+
+ D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
+ m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast(m_swap_chain_buffers.size()));
+
+ swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PRESENT);
+ g_d3d12_context->ExecuteCommandList(false);
+
+ const UINT vsync_rate = static_cast(m_vsync_mode != VsyncMode::Off);
+ if (!m_vsync && m_using_allow_tearing)
+ m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);
+ else
+ m_swap_chain->Present(vsync_rate, 0);
+}
+
+void D3D12HostDisplay::SetGPUTimingEnabled(bool enabled)
+{
+ g_d3d12_context->SetEnableGPUTiming(enabled);
+}
+
+float D3D12HostDisplay::GetAndResetAccumulatedGPUTime()
+{
+ return g_d3d12_context->GetAndResetAccumulatedGPUTime();
+}
+
+HostDisplay::AdapterAndModeList D3D12HostDisplay::StaticGetAdapterAndModeList()
+{
+ ComPtr dxgi_factory;
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+ HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(dxgi_factory.put()));
+#else
+ HRESULT hr = CreateDXGIFactory2(0, IID_PPV_ARGS(dxgi_factory.put()));
+#endif
+ if (FAILED(hr))
+ return {};
+
+ return GetAdapterAndModeList(dxgi_factory.get());
+}
+
+HostDisplay::AdapterAndModeList D3D12HostDisplay::GetAdapterAndModeList(IDXGIFactory* dxgi_factory)
+{
+ AdapterAndModeList adapter_info;
+ ComPtr current_adapter;
+ while (SUCCEEDED(dxgi_factory->EnumAdapters(static_cast(adapter_info.adapter_names.size()),
+ current_adapter.put())))
+ {
+ DXGI_ADAPTER_DESC adapter_desc;
+ std::string adapter_name;
+ if (SUCCEEDED(current_adapter->GetDesc(&adapter_desc)))
+ {
+ char adapter_name_buffer[128];
+ const int name_length = WideCharToMultiByte(CP_UTF8, 0, adapter_desc.Description,
+ static_cast(std::wcslen(adapter_desc.Description)),
+ adapter_name_buffer, std::size(adapter_name_buffer), 0, nullptr);
+ if (name_length >= 0)
+ adapter_name.assign(adapter_name_buffer, static_cast(name_length));
+ else
+ adapter_name.assign("(Unknown)");
+ }
+ else
+ {
+ adapter_name.assign("(Unknown)");
+ }
+
+ if (adapter_info.fullscreen_modes.empty())
+ {
+ ComPtr output;
+ if (SUCCEEDED(current_adapter->EnumOutputs(0, &output)))
+ {
+ UINT num_modes = 0;
+ if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr)))
+ {
+ std::vector modes(num_modes);
+ if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, modes.data())))
+ {
+ for (const DXGI_MODE_DESC& mode : modes)
+ {
+ adapter_info.fullscreen_modes.push_back(StringUtil::StdStringFromFormat(
+ "%u x %u @ %f hz", mode.Width, mode.Height,
+ static_cast(mode.RefreshRate.Numerator) / static_cast(mode.RefreshRate.Denominator)));
+ }
+ }
+ }
+ }
+ }
+
+ // handle duplicate adapter names
+ if (std::any_of(adapter_info.adapter_names.begin(), adapter_info.adapter_names.end(),
+ [&adapter_name](const std::string& other) { return (adapter_name == other); }))
+ {
+ std::string original_adapter_name = std::move(adapter_name);
+
+ u32 current_extra = 2;
+ do
+ {
+ adapter_name = StringUtil::StdStringFromFormat("%s (%u)", original_adapter_name.c_str(), current_extra);
+ current_extra++;
+ } while (std::any_of(adapter_info.adapter_names.begin(), adapter_info.adapter_names.end(),
+ [&adapter_name](const std::string& other) { return (adapter_name == other); }));
+ }
+
+ adapter_info.adapter_names.push_back(std::move(adapter_name));
+ }
+
+ return adapter_info;
+}
diff --git a/pcsx2/Frontend/D3D12HostDisplay.h b/pcsx2/Frontend/D3D12HostDisplay.h
new file mode 100644
index 0000000000..f0dcb7ab41
--- /dev/null
+++ b/pcsx2/Frontend/D3D12HostDisplay.h
@@ -0,0 +1,105 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+
+#include "common/Pcsx2Defs.h"
+#include "common/RedtapeWindows.h"
+
+#include "common/D3D12/DescriptorHeapManager.h"
+#include "common/D3D12/Texture.h"
+#include "common/WindowInfo.h"
+
+#include "HostDisplay.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class D3D12HostDisplay : public HostDisplay
+{
+public:
+ template
+ using ComPtr = wil::com_ptr_nothrow;
+
+ D3D12HostDisplay();
+ ~D3D12HostDisplay();
+
+ RenderAPI GetRenderAPI() const override;
+ void* GetRenderDevice() const override;
+ void* GetRenderContext() const override;
+ void* GetRenderSurface() const override;
+
+ bool HasRenderDevice() const override;
+ bool HasRenderSurface() const override;
+
+ bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, VsyncMode vsync, bool threaded_presentation, bool debug_device) override;
+ bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) override;
+ void DestroyRenderDevice() override;
+
+ bool MakeRenderContextCurrent() override;
+ bool DoneRenderContextCurrent() override;
+
+ bool ChangeRenderWindow(const WindowInfo& new_wi) override;
+ void ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
+ bool SupportsFullscreen() const override;
+ bool IsFullscreen() override;
+ bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
+ AdapterAndModeList GetAdapterAndModeList() override;
+ void DestroyRenderSurface() override;
+ std::string GetDriverInfo() const override;
+
+ std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override;
+ void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) override;
+
+ bool GetHostRefreshRate(float* refresh_rate) override;
+
+ void SetVSync(VsyncMode mode) override;
+
+ bool BeginPresent(bool frame_skip) override;
+ void EndPresent() override;
+
+ void SetGPUTimingEnabled(bool enabled) override;
+ float GetAndResetAccumulatedGPUTime() override;
+
+ static AdapterAndModeList StaticGetAdapterAndModeList();
+
+protected:
+ static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory* dxgi_factory);
+
+ bool CreateImGuiContext() override;
+ void DestroyImGuiContext() override;
+ bool UpdateImGuiFontTexture() override;
+
+ bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
+ bool CreateSwapChainRTV();
+ void DestroySwapChainRTVs();
+
+ ComPtr m_dxgi_factory;
+ ComPtr m_swap_chain;
+ std::vector m_swap_chain_buffers;
+ u32 m_current_swap_chain_buffer = 0;
+
+ D3D12::DescriptorHeapManager m_imgui_descriptor_heap;
+ D3D12::DescriptorHandle m_imgui_descriptor_handle;
+
+ bool m_allow_tearing_supported = false;
+ bool m_using_allow_tearing = false;
+ bool m_vsync = true;
+};
diff --git a/pcsx2/HostDisplay.cpp b/pcsx2/HostDisplay.cpp
index de2d1c0469..53434785ca 100644
--- a/pcsx2/HostDisplay.cpp
+++ b/pcsx2/HostDisplay.cpp
@@ -37,6 +37,7 @@ const char* HostDisplay::RenderAPIToString(RenderAPI api)
#define CASE(x) case RenderAPI::x: return #x
CASE(None);
CASE(D3D11);
+ CASE(D3D12);
CASE(Metal);
CASE(Vulkan);
CASE(OpenGL);
@@ -134,6 +135,7 @@ std::string HostDisplay::GetFullscreenModeString(u32 width, u32 height, float re
#ifdef _WIN32
#include "Frontend/D3D11HostDisplay.h"
+#include "Frontend/D3D12HostDisplay.h"
#endif
#include "GS/Renderers/Metal/GSMetalCPPAccessible.h"
@@ -144,6 +146,8 @@ std::unique_ptr HostDisplay::CreateDisplayForAPI(RenderAPI api)
#ifdef _WIN32
case RenderAPI::D3D11:
return std::make_unique();
+ case RenderAPI::D3D12:
+ return std::make_unique();
#endif
#ifdef __APPLE__
case HostDisplay::RenderAPI::Metal:
diff --git a/pcsx2/HostDisplay.h b/pcsx2/HostDisplay.h
index adcbe4428f..4c031f0fe2 100644
--- a/pcsx2/HostDisplay.h
+++ b/pcsx2/HostDisplay.h
@@ -47,6 +47,7 @@ public:
None,
D3D11,
Metal,
+ D3D12,
Vulkan,
OpenGL,
OpenGLES
diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj
index 96d77371c4..cd6010bcf3 100644
--- a/pcsx2/pcsx2.vcxproj
+++ b/pcsx2/pcsx2.vcxproj
@@ -43,6 +43,7 @@
$(SolutionDir)3rdparty\cubeb\cubeb\include;$(SolutionDir)3rdparty\cubeb\include;%(AdditionalIncludeDirectories)
$(SolutionDir)3rdparty\imgui\imgui;$(SolutionDir)3rdparty\imgui\include;%(AdditionalIncludeDirectories)
$(SolutionDir)3rdparty\libzip;$(SolutionDir)3rdparty\libzip\libzip\lib;%(AdditionalIncludeDirectories)
+ $(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)
Async
Use
PrecompiledHeader.h
@@ -321,6 +322,7 @@
+
@@ -783,6 +785,7 @@
+
diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters
index 05100976e9..57390e7b2f 100644
--- a/pcsx2/pcsx2.vcxproj.filters
+++ b/pcsx2/pcsx2.vcxproj.filters
@@ -1766,6 +1766,9 @@
AppHost
+
+ Host
+
@@ -2936,6 +2939,9 @@
AppHost
+
+ Host
+
diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj
index 791f9c55bc..cc2b4116d7 100644
--- a/pcsx2/pcsx2core.vcxproj
+++ b/pcsx2/pcsx2core.vcxproj
@@ -47,6 +47,7 @@
$(SolutionDir)3rdparty\simpleini\include;%(AdditionalIncludeDirectories)
$(SolutionDir)3rdparty\sdl2\include;$(SolutionDir)3rdparty\sdl2\SDL\include;%(AdditionalIncludeDirectories)
$(SolutionDir)3rdparty\libzip;$(SolutionDir)3rdparty\libzip\libzip\lib;%(AdditionalIncludeDirectories)
+ $(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)
Async
Use
PrecompiledHeader.h
@@ -184,6 +185,7 @@
+
@@ -499,6 +501,7 @@
+
diff --git a/pcsx2/pcsx2core.vcxproj.filters b/pcsx2/pcsx2core.vcxproj.filters
index ccbcb21983..9d01dc2063 100644
--- a/pcsx2/pcsx2core.vcxproj.filters
+++ b/pcsx2/pcsx2core.vcxproj.filters
@@ -1251,6 +1251,9 @@
System\Ps2\EmotionEngine\EE\Dynarec
+
+ Host
+
@@ -2065,6 +2068,9 @@
System\Ps2\EmotionEngine\EE\Dynarec
+
+ Host
+