Video: the frontend's frame targets in place of swap chains

The frontend owns the window and presents, so the D3D11, D3D12 and
Vulkan backends draw their picture into a FrameOutput, a render target
the frontend reads where it is, rather than a swap chain. D3D11 hands
over one texture at pixel shader slot 0. D3D12 negotiates version 2 of
its interface and keeps one texture per sync index, handed over with
the fence its picture completes on, falling back to set_texture on
version 1. Vulkan keeps one image per sync index and hands it over with
set_image, so the fake swap chain and the WSI calls hooked under it are
gone, and the device may come without a surface. The outputs are RGBA8
and report that format to the presenter.

The DXGI and Vulkan swap chains, the backends' standalone device
creation and exclusive fullscreen are removed.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-06 19:11:38 +01:00
1 parent fb7fb73c64
commit 919e8c5181
34 files changed
+508 -2819

No files matched your search

+1 -4
View File
@@ -1278,6 +1278,7 @@ DOLPHIN_SOURCES_CXX += \
$(CORE_DIR)/Source/Core/DolphinLibretro/Log.cpp \
$(CORE_DIR)/Source/Core/DolphinLibretro/Main.cpp \
$(CORE_DIR)/Source/Core/DolphinLibretro/Video.cpp \
$(CORE_DIR)/Source/Core/DolphinLibretro/Output.cpp \
$(CORE_DIR)/Source/Core/DolphinLibretro/VR.cpp \
$(CORE_DIR)/Source/Core/Core/ActionReplay.cpp \
$(CORE_DIR)/Source/Core/Core/ARDecrypt.cpp \
@@ -1637,7 +1638,6 @@ ifeq ($(HAVE_VULKAN),1)
$(CORE_DIR)/Source/Core/VideoBackends/Vulkan/VKGfx.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/Vulkan/VKShader.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/Vulkan/VKTexture.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/Vulkan/VKVertexFormat.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp \
@@ -1679,7 +1679,6 @@ ifeq ($(HAVE_D3D),1)
DOLPHIN_SOURCES_CXX += \
$(CORE_DIR)/Source/Core/VideoBackends/D3DCommon/D3DCommon.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3DCommon/Shader.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/D3DBase.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/D3DBoundingBox.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/D3DMain.cpp \
@@ -1687,7 +1686,6 @@ ifeq ($(HAVE_D3D),1)
$(CORE_DIR)/Source/Core/VideoBackends/D3D/D3DPerfQuery.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/D3DGfx.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/D3DState.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/D3DSwapChain.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/DXPipeline.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D/DXShader.cpp \
@@ -1696,7 +1694,6 @@ ifeq ($(HAVE_D3D),1)
$(CORE_DIR)/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D12/D3D12StreamBuffer.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D12/D3D12SwapChain.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D12/DescriptorAllocator.cpp \
$(CORE_DIR)/Source/Core/VideoBackends/D3D12/DescriptorHeapManager.cpp \
+244
View File
@@ -0,0 +1,244 @@
#include "DolphinLibretro/Output.h"
#include <cstring>
#include "Common/Logging/Log.h"
#include "DolphinLibretro/Common/Globals.h"
#include "VideoCommon/TextureConfig.h"
#ifdef _WIN32
#include "VideoBackends/D3D12/D3D12Gfx.h"
#endif
#ifdef HAS_VULKAN
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#endif
namespace Libretro
{
namespace Video
{
#if defined(_WIN32) || defined(HAS_VULKAN)
/* The sync indices a mask names: one past the highest set bit. */
static u32 SlotCount(unsigned mask)
{
u32 n = 0;
while (mask && n < OUTPUT_MAX_SLOTS)
{
mask >>= 1;
n++;
}
return n;
}
static TextureConfig OutputConfig(u32 width, u32 height)
{
return TextureConfig(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8,
AbstractTextureFlag_RenderTarget, AbstractTextureType::Texture_2D);
}
#endif
#ifdef _WIN32
DX11Output::DX11Output(u32 width, u32 height) : m_width(width), m_height(height)
{
}
bool DX11Output::Create()
{
m_texture = DX11::DXTexture::Create(OutputConfig(m_width, m_height), "LibretroOutput");
if (!m_texture)
{
ERROR_LOG_FMT(VIDEO, "Failed to create the output texture");
return false;
}
m_framebuffer = DX11::DXFramebuffer::Create(m_texture.get(), nullptr, {});
if (!m_framebuffer)
{
ERROR_LOG_FMT(VIDEO, "Failed to create the output framebuffer");
return false;
}
return true;
}
AbstractFramebuffer* DX11Output::Acquire()
{
/* the frontend read the last picture inside video_refresh */
return m_framebuffer.get();
}
void DX11Output::Present()
{
ID3D11ShaderResourceView* srv = m_texture->GetD3DSRV();
ID3D11RenderTargetView* null_view = nullptr;
/* none of the core's constant buffers left for the frontend's shader
* passes, which bind theirs only for the stages that use them */
ID3D11Buffer* null_buffers[3] = {nullptr, nullptr, nullptr};
DX11::D3D::context->OMSetRenderTargets(1, &null_view, nullptr);
DX11::D3D::context->PSSetShaderResources(0, 1, &srv);
DX11::D3D::context->VSSetConstantBuffers(0, 3, null_buffers);
DX11::D3D::context->PSSetConstantBuffers(0, 3, null_buffers);
DX11::D3D::context->GSSetConstantBuffers(0, 1, null_buffers);
video_cb(RETRO_HW_FRAME_BUFFER_VALID, m_width, m_height, 0);
DX11::D3D::stateman->Restore();
}
DX12Output::DX12Output(retro_hw_render_interface_d3d12* iface, u32 width, u32 height)
: m_iface(iface), m_width(width), m_height(height), m_current(0),
m_v2(iface->interface_version >= RETRO_HW_RENDER_INTERFACE_D3D12_VERSION_2)
{
}
bool DX12Output::Create()
{
const unsigned mask = m_v2 ? m_iface->get_sync_index_mask(m_iface->handle) : 1u;
const u32 count = SlotCount(mask);
u32 i;
for (i = 0; i < count; i++)
{
if (!(mask & (1u << i)))
continue;
m_textures[i] = DX12::DXTexture::Create(OutputConfig(m_width, m_height), "LibretroOutput");
if (!m_textures[i])
{
ERROR_LOG_FMT(VIDEO, "Failed to create output texture {}", i);
return false;
}
m_framebuffers[i] = DX12::DXFramebuffer::Create(m_textures[i].get(), nullptr, {});
if (!m_framebuffers[i])
{
ERROR_LOG_FMT(VIDEO, "Failed to create output framebuffer {}", i);
return false;
}
}
return true;
}
AbstractFramebuffer* DX12Output::Acquire()
{
if (m_v2)
{
/* the frontend is done with what it read from this index's texture
* last time it was handed over */
m_current = m_iface->get_sync_index(m_iface->handle);
m_iface->wait_sync_index(m_iface->handle);
}
return m_framebuffers[m_current].get();
}
void DX12Output::Present()
{
DX12::DXTexture* texture = m_textures[m_current].get();
u64 fence_value;
texture->TransitionToState(m_iface->required_state);
fence_value = DX12::g_dx_context->GetCurrentFenceValue();
DX12::Gfx::GetInstance()->ExecuteCommandList(false);
if (m_v2)
{
m_iface->set_texture_fenced(m_iface->handle, texture->GetResource(),
DXGI_FORMAT_R8G8B8A8_UNORM, DX12::g_dx_context->GetFence(),
fence_value);
}
else
{
m_iface->set_texture(m_iface->handle, texture->GetResource(), DXGI_FORMAT_R8G8B8A8_UNORM);
}
video_cb(RETRO_HW_FRAME_BUFFER_VALID, m_width, m_height, 0);
}
#endif
#ifdef HAS_VULKAN
VKOutput::VKOutput(retro_hw_render_interface_vulkan* iface, u32 width, u32 height)
: m_iface(iface), m_width(width), m_height(height), m_current(0)
{
memset(m_images, 0, sizeof(m_images));
}
bool VKOutput::Create()
{
const unsigned mask = m_iface->get_sync_index_mask(m_iface->handle);
const u32 count = SlotCount(mask);
u32 i;
for (i = 0; i < count; i++)
{
retro_vulkan_image* image = &m_images[i];
if (!(mask & (1u << i)))
continue;
m_textures[i] = Vulkan::VKTexture::Create(OutputConfig(m_width, m_height), "LibretroOutput");
if (!m_textures[i])
{
ERROR_LOG_FMT(VIDEO, "Failed to create output image {}", i);
return false;
}
m_framebuffers[i] = Vulkan::VKFramebuffer::Create(m_textures[i].get(), nullptr, {});
if (!m_framebuffers[i])
{
ERROR_LOG_FMT(VIDEO, "Failed to create output framebuffer {}", i);
return false;
}
image->image_view = m_textures[i]->GetView();
image->image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
image->create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image->create_info.image = m_textures[i]->GetImage();
image->create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image->create_info.format = m_textures[i]->GetVkFormat();
image->create_info.components.r = VK_COMPONENT_SWIZZLE_R;
image->create_info.components.g = VK_COMPONENT_SWIZZLE_G;
image->create_info.components.b = VK_COMPONENT_SWIZZLE_B;
image->create_info.components.a = VK_COMPONENT_SWIZZLE_A;
image->create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image->create_info.subresourceRange.baseMipLevel = 0;
image->create_info.subresourceRange.levelCount = 1;
image->create_info.subresourceRange.baseArrayLayer = 0;
image->create_info.subresourceRange.layerCount = 1;
}
return true;
}
AbstractFramebuffer* VKOutput::Acquire()
{
Vulkan::VKTexture* texture;
/* the frontend is done with what it read from this index's image last
* time it was handed over */
m_iface->wait_sync_index(m_iface->handle);
m_current = m_iface->get_sync_index(m_iface->handle);
texture = m_textures[m_current].get();
if (!texture)
return nullptr;
/* the last picture's contents are not kept */
texture->OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
texture->TransitionToLayout(Vulkan::g_command_buffer_mgr->GetCurrentCommandBuffer(),
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
return m_framebuffers[m_current].get();
}
void VKOutput::Present()
{
Vulkan::VKTexture* texture = m_textures[m_current].get();
if (!texture)
{
Vulkan::g_command_buffer_mgr->SubmitCommandBuffer(false, false, true);
return;
}
/* the layout the frontend reads it in; submitted here, on this thread,
* before the frontend is told, so its reads follow on the queue */
texture->TransitionToLayout(Vulkan::g_command_buffer_mgr->GetCurrentCommandBuffer(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
Vulkan::g_command_buffer_mgr->SubmitCommandBuffer(false, false, true);
m_iface->set_image(m_iface->handle, &m_images[m_current], 0, nullptr, m_iface->queue_index);
video_cb(RETRO_HW_FRAME_BUFFER_VALID, m_width, m_height, 0);
}
#endif
} // namespace Video
} // namespace Libretro
+94
View File
@@ -0,0 +1,94 @@
#pragma once
#include <memory>
#include "DolphinLibretro/Video.h"
#include "VideoCommon/FrameOutput.h"
namespace Libretro
{
namespace Video
{
/* What each sync index of the frontend can name: the frontend reads the
* picture where it is drawn, so there is one target per index the
* frontend can be reading from while the next picture is drawn. */
enum
{
OUTPUT_MAX_SLOTS = 32
};
#ifdef _WIN32
/* Direct3D 11: one target, which the frontend reads from pixel shader
* slot 0 while video_refresh runs. */
class DX11Output final : public FrameOutput
{
public:
DX11Output(u32 width, u32 height);
bool Create();
AbstractFramebuffer* Acquire() override;
void Present() override;
u32 GetWidth() const override { return m_width; }
u32 GetHeight() const override { return m_height; }
AbstractTextureFormat GetFormat() const override { return AbstractTextureFormat::RGBA8; }
private:
std::unique_ptr<DX11::DXTexture> m_texture;
std::unique_ptr<DX11::DXFramebuffer> m_framebuffer;
u32 m_width;
u32 m_height;
};
/* Direct3D 12: with version 2 of the interface one target per sync
* index, handed over with the fence its picture completes on and read
* where it is; with version 1 one target, which the frontend copies. */
class DX12Output final : public FrameOutput
{
public:
DX12Output(retro_hw_render_interface_d3d12* iface, u32 width, u32 height);
bool Create();
AbstractFramebuffer* Acquire() override;
void Present() override;
u32 GetWidth() const override { return m_width; }
u32 GetHeight() const override { return m_height; }
AbstractTextureFormat GetFormat() const override { return AbstractTextureFormat::RGBA8; }
private:
std::unique_ptr<DX12::DXTexture> m_textures[OUTPUT_MAX_SLOTS];
std::unique_ptr<DX12::DXFramebuffer> m_framebuffers[OUTPUT_MAX_SLOTS];
retro_hw_render_interface_d3d12* m_iface;
u32 m_width;
u32 m_height;
u32 m_current;
bool m_v2;
};
#endif
#ifdef HAS_VULKAN
/* Vulkan: one image per sync index, read by the frontend where it is,
* in shader-read layout, on the queue both submit to. */
class VKOutput final : public FrameOutput
{
public:
VKOutput(retro_hw_render_interface_vulkan* iface, u32 width, u32 height);
bool Create();
AbstractFramebuffer* Acquire() override;
void Present() override;
u32 GetWidth() const override { return m_width; }
u32 GetHeight() const override { return m_height; }
AbstractTextureFormat GetFormat() const override { return AbstractTextureFormat::RGBA8; }
private:
retro_vulkan_image m_images[OUTPUT_MAX_SLOTS];
std::unique_ptr<Vulkan::VKTexture> m_textures[OUTPUT_MAX_SLOTS];
std::unique_ptr<Vulkan::VKFramebuffer> m_framebuffers[OUTPUT_MAX_SLOTS];
retro_hw_render_interface_vulkan* m_iface;
u32 m_width;
u32 m_height;
u32 m_current;
};
#endif
} // namespace Video
} // namespace Libretro
+50 -42
View File
@@ -3,6 +3,7 @@
#include "VideoBackends/Vulkan/VulkanLoader.h"
#endif
#include "DolphinLibretro/Video.h"
#include "DolphinLibretro/Output.h"
#include <cassert>
#include <memory>
@@ -53,7 +54,6 @@
#ifdef HAS_VULKAN
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/VKSwapChain.h"
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKGfx.h"
@@ -231,6 +231,22 @@ bool SetHWRender(retro_hw_context_type type, const int version_major, const int
hw_render.version_minor = 0;
if (environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
{
/* version 2 of the interface, when the frontend has it: the frame
* is read where it is drawn, not copied */
static const struct retro_hw_render_context_negotiation_interface_d3d12 negotiation = {
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D12,
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D12_VERSION,
RETRO_HW_RENDER_INTERFACE_D3D12_VERSION_2};
struct retro_hw_render_context_negotiation_interface probe;
probe.interface_type = RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D12;
probe.interface_version = 0;
if (environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT,
&probe) &&
probe.interface_version >= 1)
{
environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE,
(void*)&negotiation);
}
Config::SetBase(Config::MAIN_GFX_BACKEND, "D3D12");
success = true;
}
@@ -292,11 +308,6 @@ void ContextReset(void)
return;
}
Vk::SetHWRenderInterface(vulkan);
int efbScale = Libretro::Options::GetCached<int>(
Libretro::Options::gfx_settings::EFB_SCALE, 1);
Vk::SetSurfaceSize(GetAdjustedBaseWidth() * efbScale,
GetAdjustedBaseHeight() * efbScale);
}
#endif
@@ -357,11 +368,12 @@ void ContextReset(void)
d3d11->FillD3DBackendInfo();
UpdateActiveConfig();
std::unique_ptr<DX11SwapChain> swap_chain = std::make_unique<DX11SwapChain>(
wsi, GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale,
nullptr, nullptr);
std::unique_ptr<DX11Output> output = std::make_unique<DX11Output>(
GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale);
if (!output->Create())
return;
auto gfx = std::make_unique<DX11::Gfx>(std::move(swap_chain), wsi.render_surface_scale);
auto gfx = std::make_unique<DX11::Gfx>(std::move(output), wsi.render_surface_scale);
auto vertex_manager = std::make_unique<DX11::VertexManager>();
auto perf_query = std::make_unique<DX11::PerfQuery>();
auto bounding_box = std::make_unique<DX11::D3DBoundingBox>();
@@ -387,10 +399,12 @@ void ContextReset(void)
return;
}
if (d3d12->interface_version != RETRO_HW_RENDER_INTERFACE_D3D12_VERSION)
if (d3d12->interface_version != RETRO_HW_RENDER_INTERFACE_D3D12_VERSION &&
d3d12->interface_version != RETRO_HW_RENDER_INTERFACE_D3D12_VERSION_2)
{
ERROR_LOG_FMT(VIDEO, "HW render interface mismatch, expected {}, got {}!",
RETRO_HW_RENDER_INTERFACE_D3D12_VERSION, d3d12->interface_version);
ERROR_LOG_FMT(VIDEO, "HW render interface mismatch, expected {} or {}, got {}!",
RETRO_HW_RENDER_INTERFACE_D3D12_VERSION,
RETRO_HW_RENDER_INTERFACE_D3D12_VERSION_2, d3d12->interface_version);
return;
}
@@ -434,17 +448,15 @@ void ContextReset(void)
UpdateActiveConfig();
auto swap_chain = std::make_unique<DX12SwapChain>(
wsi, GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale, d3d12);
if (!swap_chain->Initialize())
std::unique_ptr<DX12Output> output = std::make_unique<DX12Output>(
d3d12, GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale);
if (!output->Create())
{
ERROR_LOG_FMT(VIDEO, "Failed to initialize swap chain buffers");
DX12::DXContext::Destroy();
return;
}
auto gfx = std::make_unique<DX12::Gfx>(std::move(swap_chain), wsi.render_surface_scale);
auto gfx = std::make_unique<DX12::Gfx>(std::move(output), wsi.render_surface_scale);
auto vertex_manager = std::make_unique<DX12::VertexManager>();
auto perf_query = std::make_unique<DX12::PerfQuery>();
auto bounding_box = std::make_unique<DX12::D3D12BoundingBox>();
@@ -479,18 +491,12 @@ bool Video_InitializeBackend()
#ifdef HAS_VULKAN
if (hw_render.context_type == RETRO_HW_CONTEXT_VULKAN)
{
bool enable_surface = wsi.type != WindowSystemType::Headless;
VkSurfaceKHR surface = Libretro::Video::Vk::GetSurface();
// Since VulkanContext maintains a copy of the device features and properties, we can use this
// to initialize the backend information, so that we don't need to enumerate everything again.
Vulkan::VulkanContext::PopulateBackendInfoFeatures(&g_backend_info, Vulkan::g_vulkan_context->GetPhysicalDevice(),
Vulkan::g_vulkan_context->GetDeviceInfo());
Vulkan::VulkanContext::PopulateBackendInfoMultisampleModes(
&g_backend_info, Vulkan::g_vulkan_context->GetPhysicalDevice(), Vulkan::g_vulkan_context->GetDeviceInfo());
g_backend_info.bSupportsExclusiveFullscreen =
enable_surface && Vulkan::g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface);
UpdateActiveConfig();
// Remaining classes are also dependent on object cache.
@@ -501,23 +507,9 @@ bool Video_InitializeBackend()
return false;
}
// Create swap chain. This has to be done early so that the target size is correct for auto-scale.
std::unique_ptr<Vulkan::SwapChain> swap_chain;
if (surface != VK_NULL_HANDLE)
{
swap_chain = Vulkan::SwapChain::Create(wsi, surface, g_ActiveConfig.bVSyncActive);
if (!swap_chain)
{
PanicAlertFmt("Failed to create Vulkan swap chain.");
return false;
}
}
// Create command buffers. We do this separately because the other classes depend on it.
Vulkan::g_command_buffer_mgr = std::make_unique<Vulkan::CommandBufferManager>(g_Config.bBackendMultithreading);
size_t swapchain_image_count =
surface != VK_NULL_HANDLE ? swap_chain->GetSwapChainImageCount() : 0;
if (!Vulkan::g_command_buffer_mgr->Initialize(swapchain_image_count))
if (!Vulkan::g_command_buffer_mgr->Initialize())
{
PanicAlertFmt("Failed to create Vulkan command buffers");
return false;
@@ -529,7 +521,15 @@ bool Video_InitializeBackend()
return false;
}
auto gfx = std::make_unique<Vulkan::VKGfx>(std::move(swap_chain), wsi.render_surface_scale);
/* where the picture goes: the frontend's sync images, sized as the
* frame the frontend was told of */
std::unique_ptr<VKOutput> output = std::make_unique<VKOutput>(
Vk::GetHWRenderInterface(), GetAdjustedBaseWidth() * efbScale,
GetAdjustedBaseHeight() * efbScale);
if (!output->Create())
return false;
auto gfx = std::make_unique<Vulkan::VKGfx>(std::move(output), wsi.render_surface_scale);
auto vertex_manager = std::make_unique<Vulkan::VertexManager>();
auto perf_query = std::make_unique<Vulkan::PerfQuery>();
auto bounding_box = std::make_unique<Vulkan::VKBoundingBox>();
@@ -671,7 +671,7 @@ static bool CreateDeviceInternal(
Vulkan::LoadVulkanLibrary();
Init(instance, gpu, surface, get_instance_proc_addr, required_device_extensions,
Init(instance, gpu, get_instance_proc_addr, required_device_extensions,
num_required_device_extensions, required_device_layers, num_required_device_layers,
required_features, create_device_wrapper, opaque);
@@ -708,8 +708,16 @@ static bool CreateDeviceInternal(
context->device = Vulkan::g_vulkan_context->GetDevice();
context->queue = Vulkan::g_vulkan_context->GetGraphicsQueue();
context->queue_family_index = Vulkan::g_vulkan_context->GetGraphicsQueueFamilyIndex();
/* the frontend presents; the queue the surface takes may be another
* family's */
context->presentation_queue = context->queue;
context->presentation_queue_family_index = context->queue_family_index;
if (Vulkan::g_vulkan_context->GetPresentQueue() != VK_NULL_HANDLE)
{
context->presentation_queue = Vulkan::g_vulkan_context->GetPresentQueue();
context->presentation_queue_family_index =
Vulkan::g_vulkan_context->GetPresentQueueFamilyIndex();
}
return true;
}
+1 -163
View File
@@ -18,16 +18,15 @@
#include "VideoBackends/D3D/D3DState.h"
#include "VideoBackends/D3D/DXShader.h"
#include "VideoBackends/D3D/DXTexture.h"
#include "VideoBackends/D3D/D3DSwapChain.h"
#include "VideoBackends/D3D12/DX12Context.h"
#include "VideoBackends/D3D12/DX12Texture.h"
#include "VideoBackends/D3D12/D3D12SwapChain.h"
#endif
#ifdef HAS_VULKAN
#ifndef __APPLE__
#include "VideoBackends/Vulkan/VulkanLoader.h"
#endif
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoBackends/Vulkan/VKTexture.h"
#include "DolphinLibretro/Vulkan.h"
#include <libretro_vulkan.h>
#endif
@@ -77,167 +76,6 @@ public:
}
};
#ifdef _WIN32
class DX11SwapChain : public DX11::SwapChain
{
public:
DX11SwapChain(const WindowSystemInfo& wsi, int width, int height,
IDXGIFactory* dxgi_factory, ID3D11Device* d3d_device)
: DX11::SwapChain(wsi, dxgi_factory, d3d_device)
{
m_width = width;
m_height = height;
m_stereo = WantsStereo();
CreateSwapChainBuffers();
}
bool Present() override
{
auto* tex = GetTexture();
if (!tex)
{
ERROR_LOG_FMT(VIDEO, "Present aborted: no swap chain texture");
return false;
}
auto* srv = tex->GetD3DSRV();
if (!srv)
{
ERROR_LOG_FMT(VIDEO, "Present aborted: no SRV for swap chain texture");
return false;
}
ID3D11RenderTargetView* nullView = nullptr;
DX11::D3D::context->OMSetRenderTargets(1, &nullView, nullptr);
DX11::D3D::context->PSSetShaderResources(0, 1, &srv);
// None of the core's constant buffers left for the frontend's shader
// passes, which bind theirs only for the stages that use them: a pass
// read the core's 64 bytes at the vertex shader's slot 1 (D3D11)
ID3D11Buffer* null_buffers[3] = {};
DX11::D3D::context->VSSetConstantBuffers(0, 3, null_buffers);
DX11::D3D::context->PSSetConstantBuffers(0, 3, null_buffers);
DX11::D3D::context->GSSetConstantBuffers(0, 1, null_buffers);
Libretro::Video::video_cb(RETRO_HW_FRAME_BUFFER_VALID,
m_width, m_height,
m_width);
DX11::D3D::stateman->Restore();
return true;
}
protected:
bool CreateSwapChainBuffers() override
{
TextureConfig config(m_width, m_height, 1, 1, 1,
AbstractTextureFormat::RGBA8,
AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2D);
auto tex = DX11::DXTexture::Create(config, "LibretroSwapChainTexture");
if (!tex)
{
ERROR_LOG_FMT(VIDEO, "Backbuffer texture creation failed");
return false;
}
SetTexture(std::move(tex));
auto fb = DX11::DXFramebuffer::Create(GetTexture(), nullptr, {});
if (!fb)
{
ERROR_LOG_FMT(VIDEO, "Backbuffer framebuffer creation failed");
return false;
}
SetFramebuffer(std::move(fb));
return true;
}
};
class DX12SwapChain : public DX12::SwapChain
{
public:
DX12SwapChain(const WindowSystemInfo& wsi, int width, int height,
retro_hw_render_interface_d3d12* d3d12_interface)
: DX12::SwapChain(wsi, nullptr, nullptr),
m_d3d12_interface(d3d12_interface)
{
m_width = width;
m_height = height;
}
bool Initialize()
{
return CreateSwapChainBuffers();
}
bool Present() override
{
if (m_buffers.empty() || !m_buffers[m_current_buffer].texture)
{
ERROR_LOG_FMT(VIDEO, "Present aborted: no swap chain texture");
return false;
}
auto* texture = m_buffers[m_current_buffer].texture.get();
texture->TransitionToState(m_d3d12_interface->required_state);
m_d3d12_interface->set_texture(
m_d3d12_interface->handle,
texture->GetResource(),
DXGI_FORMAT_R8G8B8A8_UNORM
);
Libretro::Video::video_cb(RETRO_HW_FRAME_BUFFER_VALID,
m_width, m_height,
m_width);
return true;
}
protected:
bool CreateSwapChainBuffers() override
{
m_buffers.clear();
m_buffers.resize(1);
TextureConfig config(m_width, m_height, 1, 1, 1,
AbstractTextureFormat::RGBA8,
AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2D);
m_buffers[0].texture = DX12::DXTexture::Create(config, "LibretroSwapChainTexture");
if (!m_buffers[0].texture)
{
ERROR_LOG_FMT(VIDEO, "Backbuffer texture creation failed");
return false;
}
m_buffers[0].framebuffer = DX12::DXFramebuffer::Create(
m_buffers[0].texture.get(), nullptr, {});
if (!m_buffers[0].framebuffer)
{
ERROR_LOG_FMT(VIDEO, "Backbuffer framebuffer creation failed");
return false;
}
m_current_buffer = 0;
return true;
}
void DestroySwapChainBuffers() override
{
m_buffers.clear();
}
private:
retro_hw_render_interface_d3d12* m_d3d12_interface;
};
#endif
#ifdef HAS_VULKAN
namespace Vk
{
+10 -329
View File
@@ -2,7 +2,6 @@
// must be first
#include "VideoBackends/Vulkan/VulkanLoader.h"
#include <cassert>
#include <cstring>
#include <libretro_vulkan.h>
#include <vector>
@@ -16,21 +15,15 @@
#include <unordered_set>
#include <string>
/* The instance, device and surface are the frontend's, and so is the
* queue, which the frontend submits to from its own thread. */
#define LIBRETRO_VK_WARP_LIST() \
LIBRETRO_VK_WARP_FUNC(vkDestroyInstance); \
LIBRETRO_VK_WARP_FUNC(vkCreateDevice); \
LIBRETRO_VK_WARP_FUNC(vkDestroyDevice); \
LIBRETRO_VK_WARP_FUNC(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); \
LIBRETRO_VK_WARP_FUNC(vkDestroySurfaceKHR); \
LIBRETRO_VK_WARP_FUNC(vkCreateSwapchainKHR); \
LIBRETRO_VK_WARP_FUNC(vkGetSwapchainImagesKHR); \
LIBRETRO_VK_WARP_FUNC(vkAcquireNextImageKHR); \
LIBRETRO_VK_WARP_FUNC(vkQueuePresentKHR); \
LIBRETRO_VK_WARP_FUNC(vkDestroySwapchainKHR); \
LIBRETRO_VK_WARP_FUNC(vkQueueSubmit); \
LIBRETRO_VK_WARP_FUNC(vkQueueWaitIdle); \
LIBRETRO_VK_WARP_FUNC(vkCmdPipelineBarrier); \
LIBRETRO_VK_WARP_FUNC(vkCreateRenderPass)
LIBRETRO_VK_WARP_FUNC(vkQueueWaitIdle)
#define LIBRETRO_VK_WARP_FUNC(x) \
extern PFN_##x x; \
@@ -41,15 +34,6 @@ LIBRETRO_VK_WARP_FUNC(vkGetDeviceProcAddr);
LIBRETRO_VK_WARP_LIST();
extern PFN_vkCreateInstance vkCreateInstance;
extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
extern PFN_vkAllocateMemory vkAllocateMemory;
extern PFN_vkBindImageMemory vkBindImageMemory;
extern PFN_vkCreateImage vkCreateImage;
extern PFN_vkDestroyImage vkDestroyImage;
extern PFN_vkCreateImageView vkCreateImageView;
extern PFN_vkDestroyImageView vkDestroyImageView;
extern PFN_vkFreeMemory vkFreeMemory;
namespace Libretro
{
@@ -63,9 +47,6 @@ static struct
{
VkInstance instance;
VkPhysicalDevice gpu;
VkSurfaceKHR surface;
uint32_t width;
uint32_t height;
PFN_vkGetInstanceProcAddr get_instance_proc_addr;
const char** required_device_extensions;
unsigned num_required_device_extensions;
@@ -75,25 +56,6 @@ static struct
retro_vulkan_create_device_wrapper_t create_device_wrapper;
void* create_device_opaque;
} initInfo;
static bool DEDICATED_ALLOCATION;
struct VkSwapchainImage
{
VkImage handle;
VkDeviceMemory memory;
retro_vulkan_image retro_image;
};
struct VkSwapchainKHR_T
{
uint32_t count;
std::vector<VkSwapchainImage> images;
int current_index;
VkExtent2D extent;
};
static VkSwapchainKHR_T chain;
VkSurfaceKHR GetSurface() { return initInfo.surface; }
static VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
@@ -202,17 +164,6 @@ static VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDe
}
}
// Side flag for VMA or other paths
DEDICATED_ALLOCATION = false;
for (const char* extension_name : filtered_exts)
{
if (extension_name && !std::strcmp(extension_name, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME))
{
DEDICATED_ALLOCATION = true;
break;
}
}
// Finalize create-info with filtered lists
info.enabledLayerCount = static_cast<uint32_t>(enabled_layers.size());
info.ppEnabledLayerNames = info.enabledLayerCount ? enabled_layers.data() : nullptr;
@@ -232,192 +183,6 @@ static VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDe
return vkCreateDevice_org(physicalDevice, &info, pAllocator, pDevice);
}
static VKAPI_ATTR VkResult VKAPI_CALL
vkCreateLibretroSurfaceKHR(VkInstance instance, const void* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface)
{
*pSurface = initInfo.surface;
return VK_SUCCESS;
}
static VKAPI_ATTR VkResult VKAPI_CALL
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
VkSurfaceCapabilitiesKHR* pSurfaceCapabilities)
{
VkResult res =
vkGetPhysicalDeviceSurfaceCapabilitiesKHR_org(physicalDevice, surface, pSurfaceCapabilities);
if (res == VK_SUCCESS)
{
pSurfaceCapabilities->currentExtent.width = initInfo.width;
pSurfaceCapabilities->currentExtent.height = initInfo.height;
}
return res;
}
static bool MemoryTypeFromProperties(uint32_t typeBits, VkFlags requirements_mask,
uint32_t* typeIndex)
{
VkPhysicalDeviceMemoryProperties memory_properties;
vkGetPhysicalDeviceMemoryProperties(vulkan->gpu, &memory_properties);
// Search memtypes to find first index with those properties
for (uint32_t i = 0; i < 32; i++)
{
if ((typeBits & 1) == 1)
{
// Type is available, does it match user properties?
if ((memory_properties.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask)
{
*typeIndex = i;
return true;
}
}
typeBits >>= 1;
}
// No memory types matched, return failure
return false;
}
static VKAPI_ATTR VkResult VKAPI_CALL
vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain)
{
uint32_t swapchain_mask = vulkan->get_sync_index_mask(vulkan->handle);
chain.count = 0;
while (swapchain_mask)
{
chain.count++;
swapchain_mask >>= 1;
}
chain.images.resize(chain.count);
chain.extent = pCreateInfo->imageExtent;
for (uint32_t i = 0; i < chain.count; i++)
{
{
VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
info.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
info.imageType = VK_IMAGE_TYPE_2D;
info.format = pCreateInfo->imageFormat;
info.extent.width = pCreateInfo->imageExtent.width;
info.extent.height = pCreateInfo->imageExtent.height;
info.extent.depth = 1;
info.mipLevels = 1;
info.arrayLayers = 1;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.tiling = VK_IMAGE_TILING_OPTIMAL;
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
vkCreateImage(device, &info, pAllocator, &chain.images[i].handle);
}
VkMemoryRequirements memreq;
vkGetImageMemoryRequirements(device, chain.images[i].handle, &memreq);
VkMemoryAllocateInfo alloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
alloc.allocationSize = memreq.size;
VkMemoryDedicatedAllocateInfoKHR dedicated{
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR};
if (DEDICATED_ALLOCATION)
{
alloc.pNext = &dedicated;
dedicated.image = chain.images[i].handle;
}
MemoryTypeFromProperties(memreq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
&alloc.memoryTypeIndex);
VkResult res = vkAllocateMemory(device, &alloc, pAllocator, &chain.images[i].memory);
(void)res;
assert(res == VK_SUCCESS);
res = vkBindImageMemory(device, chain.images[i].handle, chain.images[i].memory, 0);
assert(res == VK_SUCCESS);
chain.images[i].retro_image.create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
chain.images[i].retro_image.create_info.image = chain.images[i].handle;
chain.images[i].retro_image.create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
chain.images[i].retro_image.create_info.format = pCreateInfo->imageFormat;
chain.images[i].retro_image.create_info.components = {
VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A};
chain.images[i].retro_image.create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
chain.images[i].retro_image.create_info.subresourceRange.layerCount = 1;
chain.images[i].retro_image.create_info.subresourceRange.levelCount = 1;
res = vkCreateImageView(device, &chain.images[i].retro_image.create_info, pAllocator,
&chain.images[i].retro_image.image_view);
assert(res == VK_SUCCESS);
chain.images[i].retro_image.image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
chain.current_index = -1;
*pSwapchain = (VkSwapchainKHR)&chain;
return VK_SUCCESS;
}
static VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainImagesKHR(VkDevice device,
VkSwapchainKHR swapchain_,
uint32_t* pSwapchainImageCount,
VkImage* pSwapchainImages)
{
VkSwapchainKHR_T* swapchain = (VkSwapchainKHR_T*)swapchain_;
if (pSwapchainImages)
{
assert(*pSwapchainImageCount <= swapchain->count);
for (uint32_t i = 0; i < *pSwapchainImageCount; i++)
pSwapchainImages[i] = swapchain->images[i].handle;
}
else
*pSwapchainImageCount = swapchain->count;
return VK_SUCCESS;
}
static VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImageKHR(VkDevice device,
VkSwapchainKHR swapchain,
uint64_t timeout, VkSemaphore semaphore,
VkFence fence, uint32_t* pImageIndex)
{
vulkan->wait_sync_index(vulkan->handle);
*pImageIndex = vulkan->get_sync_index(vulkan->handle);
#if 0
vulkan->set_signal_semaphore(vulkan->handle, semaphore);
#endif
return VK_SUCCESS;
}
static VKAPI_ATTR VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue,
const VkPresentInfoKHR* pPresentInfo)
{
VkSwapchainKHR_T* swapchain = (VkSwapchainKHR_T*)pPresentInfo->pSwapchains[0];
chain.current_index = pPresentInfo->pImageIndices[0];
#if 0
vulkan->set_image(vulkan->handle, &swapchain->images[pPresentInfo->pImageIndices[0]].retro_image,
pPresentInfo->waitSemaphoreCount, pPresentInfo->pWaitSemaphores,
vulkan->queue_index);
#else
vulkan->set_image(vulkan->handle, &swapchain->images[pPresentInfo->pImageIndices[0]].retro_image,
0, nullptr, vulkan->queue_index);
#endif
// The whole image Dolphin presented into, as the other backends hand over
// their backbuffer: the picture is letterboxed or stretched over all of it
uint32_t out_w = swapchain->extent.width;
uint32_t out_h = swapchain->extent.height;
// Fallback: negotiated init dimensions (or a sane default).
if (out_w == 0 || out_h == 0)
{
out_w = Libretro::Video::Vk::initInfo.width ? Libretro::Video::Vk::initInfo.width : 640;
out_h = Libretro::Video::Vk::initInfo.height ? Libretro::Video::Vk::initInfo.height : 480;
}
video_cb(RETRO_HW_FRAME_BUFFER_VALID, out_w, out_h, 0);
return VK_SUCCESS;
}
static VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance,
const VkAllocationCallbacks* pAllocator)
{
@@ -433,44 +198,14 @@ static VKAPI_ATTR void VKAPI_CALL vkDestroySurfaceKHR(VkInstance instance, VkSur
{
}
static VKAPI_ATTR void VKAPI_CALL vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain,
const VkAllocationCallbacks* pAllocator)
{
for (uint32_t i = 0; i < chain.count; i++)
{
vkDestroyImage(device, chain.images[i].handle, pAllocator);
vkDestroyImageView(device, chain.images[i].retro_image.image_view, pAllocator);
vkFreeMemory(device, chain.images[i].memory, pAllocator);
}
chain.images.clear();
chain.count = 0;
chain.current_index = -1;
}
static VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit(VkQueue queue, uint32_t submitCount,
const VkSubmitInfo* pSubmits, VkFence fence)
{
VkResult res = VK_SUCCESS;
VkResult res;
#if 0
for(int i = 0; i < submitCount; i++)
vulkan->set_command_buffers(vulkan->handle, pSubmits[i].commandBufferCount, pSubmits[i].pCommandBuffers);
#else
#if 1
for (uint32_t i = 0; i < submitCount; i++)
{
((VkSubmitInfo*)pSubmits)[i].waitSemaphoreCount = 0;
((VkSubmitInfo*)pSubmits)[i].pWaitSemaphores = nullptr;
((VkSubmitInfo*)pSubmits)[i].signalSemaphoreCount = 0;
((VkSubmitInfo*)pSubmits)[i].pSignalSemaphores = nullptr;
}
#endif
vulkan->lock_queue(vulkan->handle);
res = vkQueueSubmit_org(queue, submitCount, pSubmits, fence);
vulkan->unlock_queue(vulkan->handle);
#endif
return res;
}
@@ -482,46 +217,6 @@ static VKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle(VkQueue queue)
return res;
}
static VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier(
VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags,
uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers,
uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers,
uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers)
{
VkImageMemoryBarrier* barriers = (VkImageMemoryBarrier*)pImageMemoryBarriers;
for (uint32_t i = 0; i < imageMemoryBarrierCount; i++)
{
if (pImageMemoryBarriers[i].oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
{
barriers[i].oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barriers[i].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
}
if (pImageMemoryBarriers[i].newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
{
barriers[i].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barriers[i].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
}
}
return vkCmdPipelineBarrier_org(commandBuffer, srcStageMask, dstStageMask, dependencyFlags,
memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount,
pBufferMemoryBarriers, imageMemoryBarrierCount, barriers);
}
static VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass(VkDevice device,
const VkRenderPassCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkRenderPass* pRenderPass)
{
if (pCreateInfo->pAttachments[0].finalLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
((VkAttachmentDescription*)pCreateInfo->pAttachments)[0].finalLayout =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
return vkCreateRenderPass_org(device, pCreateInfo, pAllocator, pRenderPass);
}
#undef LIBRETRO_VK_WARP_FUNC
#define LIBRETRO_VK_WARP_FUNC(x) \
do \
@@ -536,13 +231,6 @@ static VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass(VkDevice device,
static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance,
const char* pName)
{
if (!strcmp(pName, "vkCreateLibretroSurfaceKHR") || !strcmp(pName, "vkCreateWin32SurfaceKHR") ||
!strcmp(pName, "vkCreateAndroidSurfaceKHR") || !strcmp(pName, "vkCreateXlibSurfaceKHR") ||
!strcmp(pName, "vkCreateXcbSurfaceKHR") || !strcmp(pName, "vkCreateWaylandSurfaceKHR"))
{
return (PFN_vkVoidFunction)vkCreateLibretroSurfaceKHR;
}
PFN_vkVoidFunction fptr = vkGetInstanceProcAddr_org(instance, pName);
if (!fptr)
return fptr;
@@ -564,19 +252,14 @@ static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice dev
return fptr;
}
void Init(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface,
void Init(VkInstance instance, VkPhysicalDevice gpu,
PFN_vkGetInstanceProcAddr get_instance_proc_addr, const char** required_device_extensions,
unsigned num_required_device_extensions, const char** required_device_layers,
unsigned num_required_device_layers, const VkPhysicalDeviceFeatures* required_features,
retro_vulkan_create_device_wrapper_t create_device_wrapper, void* opaque)
{
assert(surface);
initInfo.instance = instance;
initInfo.gpu = gpu;
initInfo.surface = surface;
initInfo.width = -1;
initInfo.height = -1;
initInfo.get_instance_proc_addr = get_instance_proc_addr;
static const char* extra_extensions[] = {"VK_KHR_get_physical_device_properties2"};
@@ -608,22 +291,20 @@ void Init(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface,
::vkCreateInstance = vkCreateInstance;
}
void SetSurfaceSize(uint32_t width, uint32_t height)
{
initInfo.width = width;
initInfo.height = height;
}
void SetHWRenderInterface(retro_hw_render_interface* hw_render_interface)
{
vulkan = (retro_hw_render_interface_vulkan*)hw_render_interface;
}
retro_hw_render_interface_vulkan* GetHWRenderInterface()
{
return vulkan;
}
void Shutdown()
{
memset(&initInfo, 0x00, sizeof(initInfo));
vulkan = NULL;
DEDICATED_ALLOCATION = false;
}
} // namespace Vk
} // namespace Video
+2 -3
View File
@@ -8,16 +8,15 @@ namespace Video
{
namespace Vk
{
void Init(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface,
void Init(VkInstance instance, VkPhysicalDevice gpu,
PFN_vkGetInstanceProcAddr get_instance_proc_addr, const char** required_device_extensions,
unsigned num_required_device_extensions, const char** required_device_layers,
unsigned num_required_device_layers, const VkPhysicalDeviceFeatures* required_features,
retro_vulkan_create_device_wrapper_t create_device_wrapper = nullptr,
void* opaque = nullptr);
void SetSurfaceSize(uint32_t width, uint32_t height);
void SetHWRenderInterface(retro_hw_render_interface* hw_render_interface);
retro_hw_render_interface_vulkan* GetHWRenderInterface();
void Shutdown();
VkSurfaceKHR GetSurface();
} // namespace Vk
} // namespace Video
} // namespace Libretro
-112
View File
@@ -29,111 +29,12 @@ ComPtr<ID3D11Device1> device1;
ComPtr<ID3D11DeviceContext> context;
D3D_FEATURE_LEVEL feature_level;
static ComPtr<ID3D11Debug> s_debug;
constexpr std::array<D3D_FEATURE_LEVEL, 3> s_supported_feature_levels{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};
bool Create(u32 adapter_index, bool enable_debug_layer)
{
PFN_D3D11_CREATE_DEVICE d3d11_create_device;
if (!s_d3d11_library.Open("d3d11.dll") ||
!s_d3d11_library.GetSymbol("D3D11CreateDevice", &d3d11_create_device))
{
PanicAlertFmtT("Failed to load d3d11.dll");
s_d3d11_library.Close();
return false;
}
if (!D3DCommon::LoadLibraries())
{
s_d3d11_library.Close();
return false;
}
dxgi_factory = D3DCommon::CreateDXGIFactory(enable_debug_layer);
if (!dxgi_factory)
{
PanicAlertFmtT("Failed to create DXGI factory");
D3DCommon::UnloadLibraries();
s_d3d11_library.Close();
return false;
}
ComPtr<IDXGIAdapter> adapter;
HRESULT hr = dxgi_factory->EnumAdapters(adapter_index, &adapter);
if (FAILED(hr))
{
WARN_LOG_FMT(VIDEO, "Adapter {} not found, using default: {}", adapter_index, DX11HRWrap(hr));
adapter = nullptr;
}
// Creating debug devices can sometimes fail if the user doesn't have the correct
// version of the DirectX SDK. If it does, simply fallback to a non-debug device.
if (enable_debug_layer)
{
hr = d3d11_create_device(adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr,
D3D11_CREATE_DEVICE_DEBUG, s_supported_feature_levels.data(),
static_cast<UINT>(s_supported_feature_levels.size()),
D3D11_SDK_VERSION, &device, &feature_level, &context);
// Debugbreak on D3D error
if (SUCCEEDED(hr) && SUCCEEDED(hr = device.As(&s_debug)))
{
ComPtr<ID3D11InfoQueue> info_queue;
if (SUCCEEDED(s_debug.As(&info_queue)))
{
info_queue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true);
info_queue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true);
D3D11_MESSAGE_ID hide[] = {D3D11_MESSAGE_ID_SETPRIVATEDATA_CHANGINGPARAMS};
D3D11_INFO_QUEUE_FILTER filter = {};
filter.DenyList.NumIDs = sizeof(hide) / sizeof(D3D11_MESSAGE_ID);
filter.DenyList.pIDList = hide;
info_queue->AddStorageFilterEntries(&filter);
}
}
else
{
WARN_LOG_FMT(VIDEO, "Debug layer requested but not available: {}", DX11HRWrap(hr));
}
}
if (!enable_debug_layer || FAILED(hr))
{
hr = d3d11_create_device(adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0,
s_supported_feature_levels.data(),
static_cast<UINT>(s_supported_feature_levels.size()),
D3D11_SDK_VERSION, &device, &feature_level, &context);
}
if (FAILED(hr))
{
PanicAlertFmtT(
"Failed to initialize Direct3D.\nMake sure your video card supports at least D3D 10.0\n{0}",
DX11HRWrap(hr));
dxgi_factory.Reset();
D3DCommon::UnloadLibraries();
s_d3d11_library.Close();
return false;
}
hr = device.As(&device1);
if (FAILED(hr))
{
WARN_LOG_FMT(VIDEO,
"Missing Direct3D 11.1 support. Logical operations will not be supported.\n{}",
DX11HRWrap(hr));
}
stateman = std::make_unique<StateManager>();
return true;
}
void Destroy()
{
stateman.reset();
@@ -148,19 +49,6 @@ void Destroy()
device1.Reset();
auto remaining_references = device.Reset();
if (s_debug)
{
--remaining_references; // the debug interface increases the refcount of the device, subtract
// that.
if (remaining_references)
{
// print out alive objects, but only if we actually have pending references
// note this will also print out internal live objects to the debug console
s_debug->ReportLiveDeviceObjects((D3D11_RLDO_FLAGS)(D3D11_RLDO_SUMMARY | D3D11_RLDO_DETAIL));
}
s_debug.Reset();
}
if (remaining_references)
ERROR_LOG_FMT(VIDEO, "Unreleased references: {}.", remaining_references);
else
-2
View File
@@ -15,7 +15,6 @@
namespace DX11
{
using Microsoft::WRL::ComPtr;
class SwapChain;
namespace D3D
{
@@ -25,7 +24,6 @@ extern ComPtr<ID3D11Device1> device1;
extern ComPtr<ID3D11DeviceContext> context;
extern D3D_FEATURE_LEVEL feature_level;
bool Create(u32 adapter_index, bool enable_debug_layer);
void Destroy();
// Returns a list of supported AA modes for the current device.
+12 -41
View File
@@ -22,12 +22,12 @@
#include "VideoBackends/D3D/D3DBase.h"
#include "VideoBackends/D3D/D3DBoundingBox.h"
#include "VideoBackends/D3D/D3DState.h"
#include "VideoBackends/D3D/D3DSwapChain.h"
#include "VideoBackends/D3D/DXPipeline.h"
#include "VideoBackends/D3D/DXShader.h"
#include "VideoBackends/D3D/DXTexture.h"
#include "VideoCommon/BPFunctions.h"
#include "VideoCommon/FrameOutput.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/RenderState.h"
@@ -36,8 +36,8 @@
namespace DX11
{
Gfx::Gfx(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale)
: m_backbuffer_scale(backbuffer_scale), m_swap_chain(std::move(swap_chain))
Gfx::Gfx(std::unique_ptr<FrameOutput> output, float backbuffer_scale)
: m_backbuffer_scale(backbuffer_scale), m_output(std::move(output))
{
}
@@ -45,7 +45,7 @@ Gfx::~Gfx() = default;
bool Gfx::IsHeadless() const
{
return !m_swap_chain;
return !m_output;
}
std::unique_ptr<AbstractTexture> Gfx::CreateTexture(const TextureConfig& config,
@@ -159,34 +159,16 @@ void Gfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u
bool Gfx::BindBackbuffer(const ClearColor& clear_color)
{
CheckForSwapChainChanges();
SetAndClearFramebuffer(m_swap_chain->GetFramebuffer(), clear_color);
AbstractFramebuffer* fb = m_output->Acquire();
if (!fb)
return false;
SetAndClearFramebuffer(fb, clear_color);
return true;
}
void Gfx::PresentBackbuffer()
{
m_swap_chain->Present();
}
void Gfx::OnConfigChanged(u32 bits)
{
AbstractGfx::OnConfigChanged(bits);
// Quad-buffer changes require swap chain recreation.
if (bits & CONFIG_CHANGE_BIT_STEREO_MODE && m_swap_chain)
m_swap_chain->SetStereo(SwapChain::WantsStereo());
if (bits & CONFIG_CHANGE_BIT_HDR && m_swap_chain)
m_swap_chain->SetHDR(SwapChain::WantsHDR());
}
void Gfx::CheckForSwapChainChanges()
{
if (!m_swap_chain->CheckForFullscreenChange())
return;
m_swap_chain->ResizeSwapChain();
g_presenter->SetBackbuffer(m_swap_chain->GetWidth(), m_swap_chain->GetHeight());
m_output->Present();
}
void Gfx::SetFramebuffer(AbstractFramebuffer* framebuffer)
@@ -251,22 +233,11 @@ void Gfx::WaitForGPUIdle()
D3D::context->Flush();
}
void Gfx::SetFullscreen(bool enable_fullscreen)
{
if (m_swap_chain)
m_swap_chain->SetFullscreen(enable_fullscreen);
}
bool Gfx::IsFullscreen() const
{
return m_swap_chain && m_swap_chain->GetFullscreen();
}
SurfaceInfo Gfx::GetSurfaceInfo() const
{
return {m_swap_chain ? static_cast<u32>(m_swap_chain->GetWidth()) : 0,
m_swap_chain ? static_cast<u32>(m_swap_chain->GetHeight()) : 0, m_backbuffer_scale,
m_swap_chain ? m_swap_chain->GetFormat() : AbstractTextureFormat::Undefined};
return {m_output ? m_output->GetWidth() : 0, m_output ? m_output->GetHeight() : 0,
m_backbuffer_scale,
m_output ? m_output->GetFormat() : AbstractTextureFormat::Undefined};
}
} // namespace DX11
+3 -9
View File
@@ -10,17 +10,17 @@
#include "VideoCommon/AbstractGfx.h"
class BoundingBox;
class FrameOutput;
namespace DX11
{
class SwapChain;
class DXTexture;
class DXFramebuffer;
class Gfx final : public ::AbstractGfx
{
public:
Gfx(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale);
Gfx(std::unique_ptr<FrameOutput> output, float backbuffer_scale);
~Gfx() override;
StateCache& GetStateCache() { return m_state_cache; }
@@ -64,22 +64,16 @@ public:
u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override;
bool BindBackbuffer(const ClearColor& clear_color = {}) override;
void PresentBackbuffer() override;
void SetFullscreen(bool enable_fullscreen) override;
bool IsFullscreen() const override;
void Flush() override;
void WaitForGPUIdle() override;
void OnConfigChanged(u32 bits) override;
SurfaceInfo GetSurfaceInfo() const override;
private:
void CheckForSwapChainChanges();
StateCache m_state_cache;
float m_backbuffer_scale;
std::unique_ptr<SwapChain> m_swap_chain;
std::unique_ptr<FrameOutput> m_output;
};
} // namespace DX11
+3 -23
View File
@@ -15,7 +15,6 @@
#include "VideoBackends/D3D/D3DBoundingBox.h"
#include "VideoBackends/D3D/D3DGfx.h"
#include "VideoBackends/D3D/D3DPerfQuery.h"
#include "VideoBackends/D3D/D3DSwapChain.h"
#include "VideoBackends/D3D/D3DVertexManager.h"
#include "VideoBackends/D3DCommon/D3DCommon.h"
@@ -144,30 +143,11 @@ void VideoBackend::FillBackendInfo()
}
}
/* The frontend owns the device and the window: Libretro::Video brings
* the backend up on the device it is handed. */
bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
{
if (!D3D::Create(g_Config.iAdapter, g_Config.bEnableValidationLayer))
return false;
FillBackendInfo();
UpdateActiveConfig();
std::unique_ptr<SwapChain> swap_chain;
if (wsi.render_surface && !(swap_chain = SwapChain::Create(wsi)))
{
PanicAlertFmtT("Failed to create D3D swap chain");
ShutdownShared();
D3D::Destroy();
return false;
}
auto gfx = std::make_unique<DX11::Gfx>(std::move(swap_chain), wsi.render_surface_scale);
auto vertex_manager = std::make_unique<VertexManager>();
auto perf_query = std::make_unique<PerfQuery>();
auto bounding_box = std::make_unique<D3DBoundingBox>();
return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
std::move(bounding_box));
return false;
}
void VideoBackend::Shutdown()
@@ -1,54 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoBackends/D3D/D3DSwapChain.h"
#include "Common/Assert.h"
#include "VideoBackends/D3D/DXTexture.h"
namespace DX11
{
SwapChain::SwapChain(const WindowSystemInfo& wsi, IDXGIFactory* dxgi_factory,
ID3D11Device* d3d_device)
: D3DCommon::SwapChain(wsi, dxgi_factory, d3d_device)
{
}
SwapChain::~SwapChain() = default;
std::unique_ptr<SwapChain> SwapChain::Create(const WindowSystemInfo& wsi)
{
std::unique_ptr<SwapChain> swap_chain =
std::make_unique<SwapChain>(wsi, D3D::dxgi_factory.Get(), D3D::device.Get());
if (!swap_chain->CreateSwapChain(WantsStereo(), WantsHDR()))
return nullptr;
return swap_chain;
}
bool SwapChain::CreateSwapChainBuffers()
{
ComPtr<ID3D11Texture2D> texture;
HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(&texture));
ASSERT_MSG(VIDEO, SUCCEEDED(hr), "Failed to get swap chain buffer: {}", DX11HRWrap(hr));
if (FAILED(hr))
return false;
m_texture = DXTexture::CreateAdopted(std::move(texture));
if (!m_texture)
return false;
m_framebuffer = DXFramebuffer::Create(m_texture.get(), nullptr, {});
if (!m_framebuffer)
return false;
return true;
}
void SwapChain::DestroySwapChainBuffers()
{
m_framebuffer.reset();
m_texture.reset();
}
} // namespace DX11
@@ -1,54 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <d3d11_4.h>
#include <memory>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/WindowSystemInfo.h"
#include "VideoBackends/D3D/D3DBase.h"
#include "VideoBackends/D3DCommon/SwapChain.h"
#include "VideoCommon/TextureConfig.h"
#ifdef __LIBRETRO__
#include "VideoBackends/D3D/DXTexture.h"
#endif
namespace DX11
{
class DXTexture;
class DXFramebuffer;
class SwapChain : public D3DCommon::SwapChain
{
public:
SwapChain(const WindowSystemInfo& wsi, IDXGIFactory* dxgi_factory, ID3D11Device* d3d_device);
~SwapChain();
static std::unique_ptr<SwapChain> Create(const WindowSystemInfo& wsi);
DXTexture* GetTexture() const { return m_texture.get(); }
DXFramebuffer* GetFramebuffer() const { return m_framebuffer.get(); }
#ifdef __LIBRETRO__
void SetTexture(std::unique_ptr<DXTexture> texture)
{
m_texture = std::move(texture);
}
void SetFramebuffer(std::unique_ptr<DXFramebuffer> buffer)
{
m_framebuffer = std::move(buffer);
}
#endif
protected:
bool CreateSwapChainBuffers() override;
void DestroySwapChainBuffers() override;
// The runtime takes care of renaming the buffers.
std::unique_ptr<DXTexture> m_texture;
std::unique_ptr<DXFramebuffer> m_framebuffer;
};
} // namespace DX11
+14 -38
View File
@@ -9,13 +9,13 @@
#include "VideoBackends/D3D12/Common.h"
#include "VideoBackends/D3D12/D3D12BoundingBox.h"
#include "VideoBackends/D3D12/D3D12PerfQuery.h"
#include "VideoBackends/D3D12/D3D12SwapChain.h"
#include "VideoBackends/D3D12/DX12Context.h"
#include "VideoBackends/D3D12/DX12Pipeline.h"
#include "VideoBackends/D3D12/DX12Shader.h"
#include "VideoBackends/D3D12/DX12Texture.h"
#include "VideoBackends/D3D12/DX12VertexFormat.h"
#include "VideoBackends/D3D12/DescriptorHeapManager.h"
#include "VideoCommon/FrameOutput.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/VideoConfig.h"
@@ -28,8 +28,8 @@ static bool UsesDynamicVertexLoader(const AbstractPipeline* pipeline)
(g_ActiveConfig.UseVSForLinePointExpand() && usage != AbstractPipelineUsage::Utility);
}
Gfx::Gfx(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale)
: m_backbuffer_scale(backbuffer_scale), m_swap_chain(std::move(swap_chain))
Gfx::Gfx(std::unique_ptr<FrameOutput> output, float backbuffer_scale)
: m_backbuffer_scale(backbuffer_scale), m_output(std::move(output))
{
m_state.root_signature = g_dx_context->GetGXRootSignature();
@@ -44,7 +44,7 @@ Gfx::~Gfx() = default;
bool Gfx::IsHeadless() const
{
return !m_swap_chain;
return !m_output;
}
std::unique_ptr<AbstractTexture> Gfx::CreateTexture(const TextureConfig& config,
@@ -370,56 +370,32 @@ void Gfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u
bool Gfx::BindBackbuffer(const ClearColor& clear_color)
{
CheckForSwapChainChanges();
SetAndClearFramebuffer(m_swap_chain->GetCurrentFramebuffer(), clear_color);
AbstractFramebuffer* fb = m_output->Acquire();
if (!fb)
return false;
SetAndClearFramebuffer(fb, clear_color);
return true;
}
void Gfx::CheckForSwapChainChanges()
{
if (!m_swap_chain->CheckForFullscreenChange())
return;
// The swap chain could be in use from a previous frame.
WaitForGPUIdle();
m_swap_chain->ResizeSwapChain();
g_presenter->SetBackbuffer(m_swap_chain->GetWidth(), m_swap_chain->GetHeight());
}
void Gfx::PresentBackbuffer()
{
/* the output leaves its texture in the state the frontend reads it in
* and submits the command list itself */
m_current_framebuffer = nullptr;
m_swap_chain->GetCurrentTexture()->TransitionToState(D3D12_RESOURCE_STATE_PRESENT);
ExecuteCommandList(false);
m_swap_chain->Present();
m_output->Present();
}
SurfaceInfo Gfx::GetSurfaceInfo() const
{
return {m_swap_chain ? static_cast<u32>(m_swap_chain->GetWidth()) : 0,
m_swap_chain ? static_cast<u32>(m_swap_chain->GetHeight()) : 0, m_backbuffer_scale,
m_swap_chain ? m_swap_chain->GetFormat() : AbstractTextureFormat::Undefined};
return {m_output ? m_output->GetWidth() : 0, m_output ? m_output->GetHeight() : 0,
m_backbuffer_scale,
m_output ? m_output->GetFormat() : AbstractTextureFormat::Undefined};
}
void Gfx::OnConfigChanged(u32 bits)
{
AbstractGfx::OnConfigChanged(bits);
// For quad-buffered stereo we need to change the layer count, so recreate the swap chain.
if (m_swap_chain && bits & CONFIG_CHANGE_BIT_STEREO_MODE)
{
ExecuteCommandList(true);
m_swap_chain->SetStereo(SwapChain::WantsStereo());
}
if (m_swap_chain && bits & CONFIG_CHANGE_BIT_HDR)
{
ExecuteCommandList(true);
m_swap_chain->SetHDR(SwapChain::WantsHDR());
}
// Wipe sampler cache if force texture filtering or anisotropy changes.
if (bits & (CONFIG_CHANGE_BIT_ANISOTROPY | CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING))
{
+4 -5
View File
@@ -7,22 +7,22 @@
#include "VideoBackends/D3D12/DescriptorAllocator.h"
#include "VideoBackends/D3D12/DescriptorHeapManager.h"
#include "VideoBackends/D3D12/D3D12SwapChain.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/Constants.h"
class FrameOutput;
namespace DX12
{
class DXFramebuffer;
class DXTexture;
class DXShader;
class DXPipeline;
class SwapChain;
class Gfx final : public ::AbstractGfx
{
public:
Gfx(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale);
Gfx(std::unique_ptr<FrameOutput> output, float backbuffer_scale);
~Gfx() override;
static Gfx* GetInstance() { return static_cast<Gfx*>(g_gfx.get()); }
@@ -138,7 +138,6 @@ private:
DirtyState_DescriptorHeaps | DirtyState_VS_SRV | DirtyState_VS_SRV_Descriptor
};
void CheckForSwapChainChanges();
void BindFramebuffer(DXFramebuffer* fb);
void SetRootSignatures();
@@ -153,7 +152,7 @@ private:
float m_backbuffer_scale;
// Owned objects
std::unique_ptr<SwapChain> m_swap_chain;
std::unique_ptr<FrameOutput> m_output;
// Current state
struct
@@ -1,79 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoBackends/D3D12/D3D12SwapChain.h"
#include "Common/Assert.h"
#include "VideoBackends/D3D12/DX12Context.h"
#include "VideoBackends/D3D12/DX12Texture.h"
namespace DX12
{
SwapChain::SwapChain(const WindowSystemInfo& wsi, IDXGIFactory* dxgi_factory,
ID3D12CommandQueue* d3d_command_queue)
: D3DCommon::SwapChain(wsi, dxgi_factory, d3d_command_queue)
{
}
SwapChain::~SwapChain() = default;
std::unique_ptr<SwapChain> SwapChain::Create(const WindowSystemInfo& wsi)
{
std::unique_ptr<SwapChain> swap_chain = std::make_unique<SwapChain>(
wsi, g_dx_context->GetDXGIFactory(), g_dx_context->GetCommandQueue());
if (!swap_chain->CreateSwapChain(WantsStereo(), WantsHDR()))
return nullptr;
return swap_chain;
}
bool SwapChain::CreateSwapChainBuffers()
{
for (u32 i = 0; i < SWAP_CHAIN_BUFFER_COUNT; i++)
{
ComPtr<ID3D12Resource> resource;
HRESULT hr = m_swap_chain->GetBuffer(i, IID_PPV_ARGS(&resource));
ASSERT_MSG(VIDEO, SUCCEEDED(hr), "Failed to get swap chain buffer {}: {}", i, DX12HRWrap(hr));
BufferResources buffer;
buffer.texture = DXTexture::CreateAdopted(resource.Get());
ASSERT_MSG(VIDEO, buffer.texture != nullptr, "Failed to create swap chain buffer texture");
if (!buffer.texture)
return false;
buffer.framebuffer = DXFramebuffer::Create(buffer.texture.get(), nullptr, {});
ASSERT_MSG(VIDEO, buffer.framebuffer != nullptr,
"Failed to create swap chain buffer framebuffer");
if (!buffer.framebuffer)
return false;
m_buffers.push_back(std::move(buffer));
}
m_current_buffer = 0;
return true;
}
void SwapChain::DestroySwapChainBuffers()
{
// Swap chain textures must be released before it can be resized, therefore we need to destroy all
// of them immediately, and not place them onto the deferred desturction queue.
for (BufferResources& res : m_buffers)
{
res.framebuffer.reset();
res.texture->DestroyResource();
res.texture.release();
}
m_buffers.clear();
}
bool SwapChain::Present()
{
if (!D3DCommon::SwapChain::Present())
return false;
m_current_buffer = (m_current_buffer + 1) % SWAP_CHAIN_BUFFER_COUNT;
return true;
}
} // namespace DX12
@@ -1,55 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <d3d12.h>
#include <memory>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/WindowSystemInfo.h"
#include "VideoBackends/D3D12/Common.h"
#include "VideoBackends/D3DCommon/SwapChain.h"
#include "VideoCommon/TextureConfig.h"
namespace DX12
{
class DXTexture;
class DXFramebuffer;
class SwapChain : public D3DCommon::SwapChain
{
public:
SwapChain(const WindowSystemInfo& wsi, IDXGIFactory* dxgi_factory,
ID3D12CommandQueue* d3d_command_queue);
~SwapChain() override;
static std::unique_ptr<SwapChain> Create(const WindowSystemInfo& wsi);
bool Present() override;
DXTexture* GetCurrentTexture() const { return m_buffers[m_current_buffer].texture.get(); }
DXFramebuffer* GetCurrentFramebuffer() const
{
return m_buffers[m_current_buffer].framebuffer.get();
}
protected:
bool CreateSwapChainBuffers() override;
void DestroySwapChainBuffers() override;
#ifndef __LIBRETRO__
private:
#endif
struct BufferResources
{
std::unique_ptr<DXTexture> texture;
std::unique_ptr<DXFramebuffer> framebuffer;
};
std::vector<BufferResources> m_buffers;
u32 m_current_buffer = 0;
};
} // namespace DX12
@@ -25,8 +25,6 @@ std::unique_ptr<DXContext> 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 decltype(&D3D12SerializeRootSignature) s_d3d12_serialize_root_signature;
DXContext::DXContext() = default;
@@ -99,37 +97,6 @@ bool DXContext::SupportsTextureFormat(DXGI_FORMAT format)
(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))
{
PanicAlertFmtT("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() &&
@@ -142,84 +109,10 @@ void DXContext::Destroy()
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<IDXGIAdapter> adapter;
HRESULT hr = m_dxgi_factory->EnumAdapters(adapter_index, &adapter);
if (FAILED(hr))
{
ERROR_LOG_FMT(VIDEO, "Adapter {} not found, using default: {}", adapter_index, DX12HRWrap(hr));
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_FMT(VIDEO, "Debug layer requested but not available: {}", DX12HRWrap(hr));
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));
ASSERT_MSG(VIDEO, SUCCEEDED(hr), "Failed to create D3D12 device: {}", DX12HRWrap(hr));
if (FAILED(hr))
return false;
if (enable_debug_layer)
{
ComPtr<ID3D12InfoQueue> info_queue;
if (SUCCEEDED(m_device.As(&info_queue)))
{
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);
D3D12_INFO_QUEUE_FILTER filter = {};
std::array<D3D12_MESSAGE_ID, 5> 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<UINT>(id_list.size());
filter.DenyList.pIDList = id_list.data();
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));
ASSERT_MSG(VIDEO, SUCCEEDED(hr), "Failed to create command queue: {}", DX12HRWrap(hr));
return SUCCEEDED(hr);
}
bool DXContext::CreateFence()
{
HRESULT hr =
@@ -593,8 +486,6 @@ bool DXContext::CreateWithExternalDevice(ID3D12Device* device, ID3D12CommandQueu
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))
{
ERROR_LOG_FMT(VIDEO, "d3d12.dll could not be loaded.");
@@ -53,9 +53,6 @@ public:
// Returns a list of AA modes.
static std::vector<u32> GetAAModes(u32 adapter_index);
// Creates new device and context.
static bool Create(u32 adapter_index, bool enable_debug_layer);
// Destroys active context.
static void Destroy();
@@ -97,6 +94,9 @@ public:
// Fence value for current command list.
u64 GetCurrentFenceValue() const { return m_current_fence_value; }
// The fence the queue signals each command list's value on.
ID3D12Fence* GetFence() const { return m_fence.Get(); }
// Last "completed" fence.
u64 GetCompletedFenceValue() const { return m_completed_fence_value; }
@@ -154,9 +154,6 @@ private:
DXContext();
bool CreateDXGIFactory(bool enable_debug_layer);
bool CreateDevice(u32 adapter_index, bool enable_debug_layer);
bool CreateCommandQueue();
bool CreateFence();
bool CreateDescriptorHeaps();
bool CreateRootSignatures();
@@ -169,7 +166,6 @@ private:
void DestroyPendingResources(CommandListResources& cmdlist);
ComPtr<IDXGIFactory> m_dxgi_factory;
ComPtr<ID3D12Debug> m_debug_interface;
ComPtr<ID3D12Device> m_device;
ComPtr<ID3D12CommandQueue> m_command_queue;
@@ -14,7 +14,6 @@
#include "VideoBackends/D3D12/D3D12BoundingBox.h"
#include "VideoBackends/D3D12/D3D12Gfx.h"
#include "VideoBackends/D3D12/D3D12PerfQuery.h"
#include "VideoBackends/D3D12/D3D12SwapChain.h"
#include "VideoBackends/D3D12/D3D12VertexManager.h"
#include "VideoBackends/D3D12/DX12Context.h"
@@ -103,42 +102,11 @@ void VideoBackend::FillBackendInfo()
}
}
/* The frontend owns the device and the window: Libretro::Video brings
* the backend up on the device and queue it is handed. */
bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
{
if (!DXContext::Create(g_Config.iAdapter, g_Config.bEnableValidationLayer))
{
PanicAlertFmtT("Failed to create D3D12 context");
return false;
}
FillBackendInfo();
UpdateActiveConfig();
if (!g_dx_context->CreateGlobalResources())
{
PanicAlertFmtT("Failed to create D3D12 global resources");
DXContext::Destroy();
ShutdownShared();
return false;
}
std::unique_ptr<SwapChain> swap_chain;
if (wsi.render_surface && !(swap_chain = SwapChain::Create(wsi)))
{
PanicAlertFmtT("Failed to create D3D swap chain");
DXContext::Destroy();
ShutdownShared();
return false;
}
// Create main wrapper instances.
auto gfx = std::make_unique<DX12::Gfx>(std::move(swap_chain), wsi.render_surface_scale);
auto vertex_manager = std::make_unique<DX12::VertexManager>();
auto perf_query = std::make_unique<DX12::PerfQuery>();
auto bounding_box = std::make_unique<DX12::D3D12BoundingBox>();
return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
std::move(bounding_box));
return false;
}
void VideoBackend::Shutdown()
@@ -1,323 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoBackends/D3DCommon/SwapChain.h"
#include <algorithm>
#include <cstdint>
#include "Common/Assert.h"
#include "Common/CommonFuncs.h"
#include "Common/HRWrap.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "VideoCommon/VideoConfig.h"
static bool IsTearingSupported(IDXGIFactory2* dxgi_factory)
{
Microsoft::WRL::ComPtr<IDXGIFactory5> factory5;
if (FAILED(dxgi_factory->QueryInterface(IID_PPV_ARGS(&factory5))))
return false;
UINT allow_tearing = 0;
return SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing,
sizeof(allow_tearing))) &&
allow_tearing != 0;
}
static bool GetFullscreenState(IDXGISwapChain* swap_chain)
{
BOOL fs = FALSE;
return swap_chain && SUCCEEDED(swap_chain->GetFullscreenState(&fs, nullptr)) && fs;
}
namespace D3DCommon
{
SwapChain::SwapChain(const WindowSystemInfo& wsi, IDXGIFactory* dxgi_factory, IUnknown* d3d_device)
: m_wsi(wsi), m_dxgi_factory(dxgi_factory), m_d3d_device(d3d_device)
{
}
SwapChain::~SwapChain()
{
// Can't destroy swap chain while it's fullscreen.
if (m_swap_chain && GetFullscreenState(m_swap_chain.Get()))
m_swap_chain->SetFullscreenState(FALSE, nullptr);
}
bool SwapChain::WantsStereo()
{
return g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer;
}
bool SwapChain::WantsHDR()
{
return g_ActiveConfig.bHDR;
}
u32 SwapChain::GetSwapChainFlags() const
{
// This flag is necessary if we want to use a flip-model swapchain without locking the framerate
return m_allow_tearing_supported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
}
bool SwapChain::CreateSwapChain(bool stereo, bool hdr)
{
RECT client_rc;
if (GetClientRect(static_cast<HWND>(m_wsi.render_surface), &client_rc))
{
m_width = client_rc.right - client_rc.left;
m_height = client_rc.bottom - client_rc.top;
}
m_stereo = false;
m_hdr = false;
// Try using the Win8 version if available.
Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory2;
HRESULT hr = m_dxgi_factory.As(&dxgi_factory2);
if (SUCCEEDED(hr))
{
m_allow_tearing_supported = IsTearingSupported(dxgi_factory2.Get());
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.Width = m_width;
swap_chain_desc.Height = m_height;
swap_chain_desc.BufferCount = SWAP_CHAIN_BUFFER_COUNT;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.SampleDesc.Quality = 0;
swap_chain_desc.Format = GetDXGIFormatForAbstractFormat(m_texture_format, false);
swap_chain_desc.Scaling = DXGI_SCALING_STRETCH;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swap_chain_desc.Stereo = stereo;
swap_chain_desc.Flags = GetSwapChainFlags();
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain1;
hr = dxgi_factory2->CreateSwapChainForHwnd(m_d3d_device.Get(),
static_cast<HWND>(m_wsi.render_surface),
&swap_chain_desc, nullptr, nullptr, &swap_chain1);
if (FAILED(hr))
{
// Flip-model discard swapchains aren't supported on Windows 8, so here we fall back to
// a sequential swapchain
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
hr = dxgi_factory2->CreateSwapChainForHwnd(m_d3d_device.Get(),
static_cast<HWND>(m_wsi.render_surface),
&swap_chain_desc, nullptr, nullptr, &swap_chain1);
}
m_swap_chain = swap_chain1;
}
// Flip-model swapchains aren't supported on Windows 7, so here we fall back to a legacy
// BitBlt-model swapchain. Note that this won't work for DX12, but systems which don't
// support the newer DXGI interface aren't going to support DX12 anyway.
if (FAILED(hr))
{
hdr = false;
DXGI_SWAP_CHAIN_DESC desc = {};
desc.BufferDesc.Width = m_width;
desc.BufferDesc.Height = m_height;
desc.BufferDesc.Format = GetDXGIFormatForAbstractFormat(m_texture_format, false);
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = SWAP_CHAIN_BUFFER_COUNT;
desc.OutputWindow = static_cast<HWND>(m_wsi.render_surface);
desc.Windowed = TRUE;
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
desc.Flags = 0;
m_allow_tearing_supported = false;
hr = m_dxgi_factory->CreateSwapChain(m_d3d_device.Get(), &desc, &m_swap_chain);
}
if (FAILED(hr))
{
PanicAlertFmt("Failed to create swap chain: {}", Common::HRWrap(hr));
return false;
}
// We handle fullscreen ourselves.
hr = m_dxgi_factory->MakeWindowAssociation(static_cast<HWND>(m_wsi.render_surface),
DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER);
if (FAILED(hr))
WARN_LOG_FMT(VIDEO, "MakeWindowAssociation() failed: {}", Common::HRWrap(hr));
m_stereo = stereo;
if (hdr)
{
// Only try to activate HDR here, to avoid failing when creating the swapchain
// (we can't know if the format is supported upfront)
Microsoft::WRL::ComPtr<IDXGISwapChain4> swap_chain4;
hr = m_swap_chain.As(&swap_chain4);
if (SUCCEEDED(hr))
{
UINT color_space_support = 0;
// Note that this should succeed even if HDR is not currently engaged on the monitor,
// but it should display fine nonetheless.
// We need to check for DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 as checking for
// scRGB always returns false (DX bug).
hr = swap_chain4->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020,
&color_space_support);
if (SUCCEEDED(hr) && (color_space_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT))
{
hr = swap_chain4->ResizeBuffers(SWAP_CHAIN_BUFFER_COUNT, 0, 0,
GetDXGIFormatForAbstractFormat(m_texture_format_hdr, false),
GetSwapChainFlags());
if (SUCCEEDED(hr))
{
hr = swap_chain4->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709);
if (SUCCEEDED(hr))
m_hdr = hdr;
}
}
}
}
if (!CreateSwapChainBuffers())
{
PanicAlertFmt("Failed to create swap chain buffers");
DestroySwapChainBuffers();
m_swap_chain.Reset();
return false;
}
return true;
}
void SwapChain::DestroySwapChain()
{
DestroySwapChainBuffers();
// Can't destroy swap chain while it's fullscreen.
if (m_swap_chain && GetFullscreenState(m_swap_chain.Get()))
m_swap_chain->SetFullscreenState(FALSE, nullptr);
m_swap_chain.Reset();
}
bool SwapChain::ResizeSwapChain()
{
DestroySwapChainBuffers();
// The swap chain fills up the size of the window if no size is specified
HRESULT hr = m_swap_chain->ResizeBuffers(SWAP_CHAIN_BUFFER_COUNT, 0, 0, DXGI_FORMAT_UNKNOWN,
GetSwapChainFlags());
if (FAILED(hr))
WARN_LOG_FMT(VIDEO, "ResizeBuffers() failed: {}", Common::HRWrap(hr));
Microsoft::WRL::ComPtr<IDXGISwapChain4> swap_chain4;
hr = m_swap_chain.As(&swap_chain4);
if (SUCCEEDED(hr))
hr = swap_chain4->SetColorSpace1(m_hdr ? DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 :
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
DXGI_SWAP_CHAIN_DESC desc;
if (SUCCEEDED(m_swap_chain->GetDesc(&desc)))
{
m_width = desc.BufferDesc.Width;
m_height = desc.BufferDesc.Height;
}
return CreateSwapChainBuffers();
}
void SwapChain::SetStereo(bool stereo)
{
if (m_stereo == stereo)
return;
DestroySwapChain();
// Do not try to re-activate HDR here if it had already failed
if (!CreateSwapChain(stereo, m_hdr))
{
PanicAlertFmt("Failed to switch swap chain stereo mode");
CreateSwapChain(false, false);
}
}
void SwapChain::SetHDR(bool hdr)
{
if (m_hdr == hdr)
return;
// NOTE: as an optimization here we could just call "ResizeSwapChain()"
// by adding some code to check if we could change the format to HDR.
DestroySwapChain();
// Do not try to re-activate stereo mode here if it had already failed
if (!CreateSwapChain(m_stereo, hdr))
{
PanicAlertFmt("Failed to switch swap chain SDR/HDR mode");
CreateSwapChain(false, false);
}
}
bool SwapChain::GetFullscreen() const
{
return GetFullscreenState(m_swap_chain.Get());
}
void SwapChain::SetFullscreen(bool request)
{
m_swap_chain->SetFullscreenState(request, nullptr);
}
bool SwapChain::CheckForFullscreenChange()
{
if (m_fullscreen_request != m_has_fullscreen)
{
HRESULT hr = m_swap_chain->SetFullscreenState(m_fullscreen_request, nullptr);
if (SUCCEEDED(hr))
{
m_has_fullscreen = m_fullscreen_request;
return true;
}
}
const bool new_fullscreen_state = GetFullscreenState(m_swap_chain.Get());
if (new_fullscreen_state != m_has_fullscreen)
{
m_has_fullscreen = new_fullscreen_state;
m_fullscreen_request = new_fullscreen_state;
return true;
}
return false;
}
bool SwapChain::Present()
{
// When using sync interval 0, it is recommended to always pass the tearing flag when it is
// supported, even when presenting in windowed mode. However, this flag cannot be used if the app
// is in fullscreen mode as a result of calling SetFullscreenState.
UINT present_flags = 0;
if (m_allow_tearing_supported && !g_ActiveConfig.bVSyncActive && !m_has_fullscreen)
present_flags |= DXGI_PRESENT_ALLOW_TEARING;
HRESULT hr = m_swap_chain->Present(static_cast<UINT>(g_ActiveConfig.bVSyncActive), present_flags);
if (FAILED(hr))
{
WARN_LOG_FMT(VIDEO, "Swap chain present failed: {}", Common::HRWrap(hr));
return false;
}
return true;
}
bool SwapChain::ChangeSurface(void* native_handle)
{
DestroySwapChain();
m_wsi.render_surface = native_handle;
// We only keep the swap chain settings (HDR/Stereo) that had successfully applied before
return CreateSwapChain(m_stereo, m_hdr);
}
} // namespace D3DCommon
@@ -1,78 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <dxgi1_5.h>
#include <wrl/client.h>
#include "Common/CommonTypes.h"
#include "Common/WindowSystemInfo.h"
#include "VideoBackends/D3DCommon/D3DCommon.h"
#include "VideoCommon/TextureConfig.h"
namespace D3DCommon
{
class SwapChain
{
public:
SwapChain(const WindowSystemInfo& wsi, IDXGIFactory* dxgi_factory, IUnknown* d3d_device);
virtual ~SwapChain();
// Sufficient buffers for triple buffering.
static const u32 SWAP_CHAIN_BUFFER_COUNT = 3;
// Returns true if the stereo mode is quad-buffering.
static bool WantsStereo();
static bool WantsHDR();
IDXGISwapChain* GetDXGISwapChain() const { return m_swap_chain.Get(); }
AbstractTextureFormat GetFormat() const
{
return m_hdr ? m_texture_format_hdr : m_texture_format;
}
u32 GetWidth() const { return m_width; }
u32 GetHeight() const { return m_height; }
// Mode switches.
bool GetFullscreen() const;
void SetFullscreen(bool request);
// Checks for loss of exclusive fullscreen.
bool CheckForFullscreenChange();
// Presents the swap chain to the screen.
virtual bool Present();
bool ChangeSurface(void* native_handle);
bool ResizeSwapChain();
void SetStereo(bool stereo);
void SetHDR(bool hdr);
protected:
u32 GetSwapChainFlags() const;
bool CreateSwapChain(bool stereo = false, bool hdr = false);
void DestroySwapChain();
virtual bool CreateSwapChainBuffers() = 0;
virtual void DestroySwapChainBuffers() = 0;
WindowSystemInfo m_wsi;
Microsoft::WRL::ComPtr<IDXGIFactory> m_dxgi_factory;
Microsoft::WRL::ComPtr<IDXGISwapChain> m_swap_chain;
Microsoft::WRL::ComPtr<IUnknown> m_d3d_device;
const AbstractTextureFormat m_texture_format = AbstractTextureFormat::RGB10_A2;
const AbstractTextureFormat m_texture_format_hdr = AbstractTextureFormat::RGBA16F;
u32 m_width = 1;
u32 m_height = 1;
bool m_stereo = false;
bool m_hdr = false;
bool m_allow_tearing_supported = false;
bool m_has_fullscreen = false;
bool m_fullscreen_request = false;
};
} // namespace D3DCommon
@@ -32,9 +32,9 @@ CommandBufferManager::~CommandBufferManager()
DestroyCommandBuffers();
}
bool CommandBufferManager::Initialize(size_t swapchain_image_count)
bool CommandBufferManager::Initialize()
{
if (!CreateCommandBuffers(swapchain_image_count))
if (!CreateCommandBuffers())
return false;
if (m_use_threaded_submission && !CreateSubmitThread())
@@ -43,18 +43,14 @@ bool CommandBufferManager::Initialize(size_t swapchain_image_count)
return true;
}
bool CommandBufferManager::CreateCommandBuffers(size_t swapchain_image_count)
bool CommandBufferManager::CreateCommandBuffers()
{
static constexpr VkSemaphoreCreateInfo semaphore_create_info = {
VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
VkDevice device = g_vulkan_context->GetDevice();
VkResult res;
for (CmdBufferResources& resources : m_command_buffers)
{
resources.init_command_buffer_used = false;
resources.semaphore_used = false;
VkCommandPoolCreateInfo pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, 0,
g_vulkan_context->GetGraphicsQueueFamilyIndex()};
@@ -86,26 +82,6 @@ bool CommandBufferManager::CreateCommandBuffers(size_t swapchain_image_count)
LOG_VULKAN_ERROR(res, "vkCreateFence failed: ");
return false;
}
res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &resources.semaphore);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
return false;
}
}
m_present_semaphores.reserve(swapchain_image_count);
for (uint32_t i = 0; i < swapchain_image_count; i++)
{
VkSemaphore present_semaphore;
res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &present_semaphore);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
return false;
}
m_present_semaphores.push_back(present_semaphore);
}
// Activate the first command buffer. BeginCommandBuffer moves forward, so start with the last
@@ -131,9 +107,6 @@ void CommandBufferManager::DestroyCommandBuffers()
for (auto& it : resources.cleanup_resources)
it();
if (resources.semaphore != VK_NULL_HANDLE)
vkDestroySemaphore(device, resources.semaphore, nullptr);
if (resources.fence != VK_NULL_HANDLE)
vkDestroyFence(device, resources.fence, nullptr);
}
@@ -145,11 +118,6 @@ void CommandBufferManager::DestroyCommandBuffers()
vkDestroyDescriptorPool(device, descriptor_pool, nullptr);
}
}
for (VkSemaphore present_semaphore : m_present_semaphores)
{
vkDestroySemaphore(device, present_semaphore, nullptr);
}
}
VkDescriptorPool CommandBufferManager::CreateDescriptorPool(u32 max_descriptor_sets)
@@ -231,8 +199,7 @@ VkDescriptorSet CommandBufferManager::AllocateDescriptorSet(VkDescriptorSetLayou
bool CommandBufferManager::CreateSubmitThread()
{
m_submit_thread.Reset("VK submission thread", [this](PendingCommandBufferSubmit submit) {
SubmitCommandBuffer(submit.command_buffer_index, submit.present_swap_chain,
submit.present_image_index);
SubmitCommandBuffer(submit.command_buffer_index);
CmdBufferResources& resources = m_command_buffers[submit.command_buffer_index];
retro_atomic_store_release_int(&resources.waiting_for_submit, 0);
});
@@ -309,9 +276,7 @@ void CommandBufferManager::WaitForCommandBufferCompletion(u32 index)
}
void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
bool wait_for_completion, bool advance_to_next_frame,
VkSwapchainKHR present_swap_chain,
uint32_t present_image_index)
bool wait_for_completion, bool advance_to_next_frame)
{
// End the current command buffer.
CmdBufferResources& resources = GetCurrentCmdBufferResources();
@@ -331,14 +296,14 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
{
retro_atomic_store_relaxed_int(&resources.waiting_for_submit, 1);
// Push to the pending submit queue.
m_submit_thread.Push({present_swap_chain, present_image_index, m_current_cmd_buffer});
m_submit_thread.Push({m_current_cmd_buffer});
}
else
{
WaitForWorkerThreadIdle();
// Pass through to normal submission path.
SubmitCommandBuffer(m_current_cmd_buffer, present_swap_chain, present_image_index);
SubmitCommandBuffer(m_current_cmd_buffer);
if (wait_for_completion)
WaitForCommandBufferCompletion(m_current_cmd_buffer);
}
@@ -389,19 +354,16 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
BeginCommandBuffer();
}
void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
VkSwapchainKHR present_swap_chain,
u32 present_image_index)
void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index)
{
CmdBufferResources& resources = m_command_buffers[command_buffer_index];
// This may be executed on the worker thread, so don't modify any state of the manager class.
uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO,
nullptr,
0,
nullptr,
&wait_bits,
nullptr,
static_cast<u32>(resources.command_buffers.size()),
resources.command_buffers.data(),
0,
@@ -414,18 +376,6 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
submit_info.pCommandBuffers = &resources.command_buffers[1];
}
if (resources.semaphore_used)
{
submit_info.pWaitSemaphores = &resources.semaphore;
submit_info.waitSemaphoreCount = 1;
}
if (present_swap_chain != VK_NULL_HANDLE)
{
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &m_present_semaphores[present_image_index];
}
VkResult res =
vkQueueSubmit(g_vulkan_context->GetGraphicsQueue(), 1, &submit_info, resources.fence);
if (res != VK_SUCCESS)
@@ -434,41 +384,6 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
PanicAlertFmt("Failed to submit command buffer: {} ({})", VkResultToString(res),
static_cast<int>(res));
}
// Do we have a swap chain to present?
if (present_swap_chain != VK_NULL_HANDLE)
{
// Should have a signal semaphore.
VkPresentInfoKHR present_info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
nullptr,
1,
&m_present_semaphores[present_image_index],
1,
&present_swap_chain,
&present_image_index,
nullptr};
m_last_present_result = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info);
if (m_last_present_result != VK_SUCCESS)
{
// VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
if (m_last_present_result != VK_ERROR_OUT_OF_DATE_KHR &&
m_last_present_result != VK_SUBOPTIMAL_KHR &&
m_last_present_result != VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
{
LOG_VULKAN_ERROR(m_last_present_result, "vkQueuePresentKHR failed: ");
}
// Don't treat VK_SUBOPTIMAL_KHR as fatal on Android. Android 10+ requires prerotation.
// See https://twitter.com/Themaister/status/1207062674011574273
#ifdef VK_USE_PLATFORM_ANDROID_KHR
if (m_last_present_result != VK_SUBOPTIMAL_KHR)
m_last_present_failed.Set();
#else
m_last_present_failed.Set();
#endif
}
}
}
void CommandBufferManager::BeginCommandBuffer()
@@ -503,7 +418,6 @@ void CommandBufferManager::BeginCommandBuffer()
// Reset upload command buffer state
resources.init_command_buffer_used = false;
resources.semaphore_used = false;
resources.fence_counter = m_next_fence_counter++;
resources.frame_index = m_current_frame;
m_current_cmd_buffer = next_buffer_index;
@@ -11,7 +11,6 @@
#include <retro_atomic.h>
#include <Common/WorkQueueThread.h>
#include "Common/Flag.h"
#include "VideoBackends/Vulkan/Constants.h"
@@ -23,7 +22,7 @@ public:
explicit CommandBufferManager(bool use_threaded_submission);
~CommandBufferManager();
bool Initialize(size_t swapchain_image_count);
bool Initialize();
// These command buffers are allocated per-frame. They are valid until the command buffer
// is submitted, after that you should call these functions again.
@@ -55,22 +54,6 @@ public:
return resources.fence_counter;
}
// Returns the semaphore for the current command buffer, which can be used to ensure the
// swap chain image is ready before the command buffer executes.
// Once you've confirmed that the semaphore will be signalled this frame, call
// `MarkCurrentCommandBufferSemaphoreUsed`.
VkSemaphore GetCurrentCommandBufferSemaphore()
{
return m_command_buffers[m_current_cmd_buffer].semaphore;
}
// Marks the current command buffer's semaphore as used, so we'll wait on it in the next
// command buffer submission.
void MarkCurrentCommandBufferSemaphoreUsed()
{
m_command_buffers[m_current_cmd_buffer].semaphore_used = true;
}
// Ensure that the worker thread has submitted any previous command buffers and is idle.
void WaitForWorkerThreadIdle();
@@ -79,13 +62,7 @@ public:
void WaitForFenceCounter(u64 fence_counter);
void SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion,
bool advance_to_next_frame = false,
VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE,
uint32_t present_image_index = 0xFFFFFFFF);
// Was the last present submitted to the queue a failure? If so, we must recreate our swapchain.
bool CheckLastPresentFail() { return m_last_present_failed.TestAndClear(); }
VkResult GetLastPresentResult() const { return m_last_present_result; }
bool advance_to_next_frame = false);
// Schedule a vulkan resource for destruction later on. This will occur when the command buffer
// is next re-used, and the GPU has finished working with the specified resource.
@@ -96,14 +73,13 @@ public:
void DeferImageViewDestruction(VkImageView object);
private:
bool CreateCommandBuffers(size_t swapchain_image_count);
bool CreateCommandBuffers();
void DestroyCommandBuffers();
bool CreateSubmitThread();
void WaitForCommandBufferCompletion(u32 command_buffer_index);
void SubmitCommandBuffer(u32 command_buffer_index, VkSwapchainKHR present_swap_chain,
u32 present_image_index);
void SubmitCommandBuffer(u32 command_buffer_index);
void BeginCommandBuffer();
VkDescriptorPool CreateDescriptorPool(u32 descriptor_sizes);
@@ -116,10 +92,8 @@ private:
VkCommandPool command_pool = VK_NULL_HANDLE;
std::array<VkCommandBuffer, 2> command_buffers = {};
VkFence fence = VK_NULL_HANDLE;
VkSemaphore semaphore = VK_NULL_HANDLE;
u64 fence_counter = 0;
bool init_command_buffer_used = false;
bool semaphore_used = false;
retro_atomic_int_t waiting_for_submit{};
u32 frame_index = 0;
@@ -150,14 +124,9 @@ private:
// Threaded command buffer execution
struct PendingCommandBufferSubmit
{
VkSwapchainKHR present_swap_chain;
u32 present_image_index;
u32 command_buffer_index;
};
Common::WorkQueueThreadSP<PendingCommandBufferSubmit> m_submit_thread;
std::vector<VkSemaphore> m_present_semaphores = {};
Common::Flag m_last_present_failed;
VkResult m_last_present_result = VK_SUCCESS;
bool m_use_threaded_submission = false;
u32 m_descriptor_set_count = DESCRIPTOR_SETS_PER_POOL;
};
+15 -146
View File
@@ -16,11 +16,11 @@
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKPipeline.h"
#include "VideoBackends/Vulkan/VKShader.h"
#include "VideoBackends/Vulkan/VKSwapChain.h"
#include "VideoBackends/Vulkan/VKTexture.h"
#include "VideoBackends/Vulkan/VKVertexFormat.h"
#include "VideoCommon/DriverDetails.h"
#include "VideoCommon/FrameOutput.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/RenderState.h"
@@ -28,8 +28,8 @@
namespace Vulkan
{
VKGfx::VKGfx(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale)
: m_swap_chain(std::move(swap_chain)), m_backbuffer_scale(backbuffer_scale)
VKGfx::VKGfx(std::unique_ptr<FrameOutput> output, float backbuffer_scale)
: m_output(std::move(output)), m_backbuffer_scale(backbuffer_scale)
{
UpdateActiveConfig();
for (SamplerState& sampler_state : m_sampler_states)
@@ -44,7 +44,7 @@ VKGfx::~VKGfx() = default;
bool VKGfx::IsHeadless() const
{
return m_swap_chain == nullptr;
return m_output == nullptr;
}
std::unique_ptr<AbstractTexture> VKGfx::CreateTexture(const TextureConfig& config,
@@ -219,141 +219,29 @@ void VKGfx::WaitForGPUIdle()
bool VKGfx::BindBackbuffer(const ClearColor& clear_color)
{
StateTracker::GetInstance()->EndRenderPass();
AbstractFramebuffer* fb;
StateTracker::GetInstance()->EndRenderPass();
g_command_buffer_mgr->WaitForWorkerThreadIdle();
// Handle host window resizes.
// Check for exclusive fullscreen request.
if (m_swap_chain->GetCurrentFullscreenState() != m_swap_chain->GetNextFullscreenState() &&
!m_swap_chain->SetFullscreenState(m_swap_chain->GetNextFullscreenState()))
{
// if it fails, don't keep trying
m_swap_chain->SetNextFullscreenState(m_swap_chain->GetCurrentFullscreenState());
}
const bool present_fail = g_command_buffer_mgr->CheckLastPresentFail();
VkResult res = present_fail ? g_command_buffer_mgr->GetLastPresentResult() :
m_swap_chain->AcquireNextImage();
if (res == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT &&
!m_swap_chain->GetCurrentFullscreenState())
{
// AMD's binary driver as of 21.3 seems to return exclusive fullscreen lost even when it was
// never requested, so long as the caller requested it to be application controlled. Handle
// this ignoring the lost result and just continuing as normal if we never acquired it.
res = VK_SUCCESS;
if (present_fail)
{
// We still need to acquire an image.
res = m_swap_chain->AcquireNextImage();
}
}
if (res != VK_SUCCESS)
{
// Execute cmdbuffer before resizing, as the last frame could still be presenting.
ExecuteCommandBuffer(false, true);
// Was this a lost exclusive fullscreen?
if (res == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
{
// The present keeps returning exclusive mode lost unless we re-create the swap chain.
INFO_LOG_FMT(VIDEO, "Lost exclusive fullscreen.");
m_swap_chain->RecreateSwapChain();
}
else if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
{
INFO_LOG_FMT(VIDEO, "Resizing swap chain due to suboptimal/out-of-date");
m_swap_chain->ResizeSwapChain();
}
else
{
ERROR_LOG_FMT(VIDEO, "Unknown present error {:#010X} {}, please report.",
std::to_underlying(res), VkResultToString(res));
m_swap_chain->RecreateSwapChain();
}
res = m_swap_chain->AcquireNextImage();
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
{
if (res == VK_ERROR_OUT_OF_DATE_KHR)
{
INFO_LOG_FMT(VIDEO, "Swapchain still out of date, will try again next frame...");
}
else
{
PanicAlertFmt("Failed to grab image from swap chain: {:#010X} {}", std::to_underlying(res),
VkResultToString(res));
}
}
}
if (!m_swap_chain->IsCurrentImageValid())
/* the output makes its image ready to draw into, outside a render pass */
fb = m_output->Acquire();
if (!fb)
return false;
// Transition from undefined (or present src, but it can be substituted) to
// color attachment ready for writing. These transitions must occur outside
// a render pass, unless the render pass declares a self-dependency.
if (auto* tex = m_swap_chain->GetCurrentTexture())
{
tex->OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
tex->TransitionToLayout(
g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
}
if(auto* chain = m_swap_chain->GetCurrentFramebuffer())
{
SetAndClearFramebuffer(chain,
ClearColor{{0.0f, 0.0f, 0.0f, 1.0f}});
}
SetAndClearFramebuffer(fb, clear_color);
return true;
}
void VKGfx::PresentBackbuffer()
{
// End drawing to backbuffer
/* the output leaves its image readable, submits and hands it on */
StateTracker::GetInstance()->EndRenderPass();
if (m_swap_chain->IsCurrentImageValid())
{
// Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
// to it have finished before present.
if (auto* tex = m_swap_chain->GetCurrentTexture())
{
tex->TransitionToLayout(
g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
}
// Submit the current command buffer, signaling rendering finished semaphore when it's done
// Because this final command buffer is rendering to the swap chain, we need to wait for
// the available semaphore to be signaled before executing the buffer. This final submission
// can happen off-thread in the background while we're preparing the next frame.
g_command_buffer_mgr->SubmitCommandBuffer(true, false, true, m_swap_chain->GetSwapChain(),
m_swap_chain->GetCurrentImageIndex());
}
else
{
g_command_buffer_mgr->SubmitCommandBuffer(true, false, true);
}
m_output->Present();
// New cmdbuffer, so invalidate state.
StateTracker::GetInstance()->InvalidateCachedState();
}
void VKGfx::SetFullscreen(bool enable_fullscreen)
{
if (!m_swap_chain->IsFullscreenSupported())
return;
m_swap_chain->SetNextFullscreenState(enable_fullscreen);
}
bool VKGfx::IsFullscreen() const
{
return m_swap_chain && m_swap_chain->GetCurrentFullscreenState();
}
void VKGfx::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion)
{
StateTracker::GetInstance()->EndRenderPass();
@@ -370,20 +258,6 @@ void VKGfx::OnConfigChanged(u32 bits)
if (bits & CONFIG_CHANGE_BIT_HOST_CONFIG)
g_object_cache->ReloadPipelineCache();
// For vsync, we need to change the present mode, which means recreating the swap chain.
if (m_swap_chain && (bits & CONFIG_CHANGE_BIT_VSYNC))
{
ExecuteCommandBuffer(false, true);
m_swap_chain->SetVSync(g_ActiveConfig.bVSyncActive);
}
// For quad-buffered stereo we need to change the layer count, so recreate the swap chain.
if (m_swap_chain && ((bits & CONFIG_CHANGE_BIT_STEREO_MODE) || (bits & CONFIG_CHANGE_BIT_HDR)))
{
ExecuteCommandBuffer(false, true);
m_swap_chain->RecreateSwapChain();
}
// Wipe sampler cache if force texture filtering or anisotropy changes.
if (bits & (CONFIG_CHANGE_BIT_ANISOTROPY | CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING))
{
@@ -392,11 +266,6 @@ void VKGfx::OnConfigChanged(u32 bits)
}
}
void VKGfx::OnSwapChainResized()
{
g_presenter->SetBackbuffer(m_swap_chain->GetWidth(), m_swap_chain->GetHeight());
}
void VKGfx::BindFramebuffer(VKFramebuffer* fb)
{
StateTracker::GetInstance()->EndRenderPass();
@@ -581,9 +450,9 @@ void VKGfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x,
SurfaceInfo VKGfx::GetSurfaceInfo() const
{
return {m_swap_chain ? m_swap_chain->GetWidth() : 1u,
m_swap_chain ? m_swap_chain->GetHeight() : 0u, m_backbuffer_scale,
m_swap_chain ? m_swap_chain->GetTextureFormat() : AbstractTextureFormat::Undefined};
return {m_output ? m_output->GetWidth() : 1u, m_output ? m_output->GetHeight() : 0u,
m_backbuffer_scale,
m_output ? m_output->GetFormat() : AbstractTextureFormat::Undefined};
}
} // namespace Vulkan
+4 -7
View File
@@ -12,9 +12,10 @@
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/Constants.h"
class FrameOutput;
namespace Vulkan
{
class SwapChain;
class StagingTexture2D;
class VKFramebuffer;
class VKPipeline;
@@ -23,7 +24,7 @@ class VKTexture;
class VKGfx final : public ::AbstractGfx
{
public:
VKGfx(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale);
VKGfx(std::unique_ptr<FrameOutput> output, float backbuffer_scale);
~VKGfx() override;
static VKGfx* GetInstance() { return static_cast<VKGfx*>(g_gfx.get()); }
@@ -50,7 +51,6 @@ public:
const void* cache_data = nullptr,
size_t cache_data_length = 0) override;
SwapChain* GetSwapChain() const { return m_swap_chain.get(); }
void Flush() override;
void WaitForGPUIdle() override;
@@ -77,8 +77,6 @@ public:
u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override;
bool BindBackbuffer(const ClearColor& clear_color = {}) override;
void PresentBackbuffer() override;
void SetFullscreen(bool enable_fullscreen) override;
bool IsFullscreen() const override;
SurfaceInfo GetSurfaceInfo() const override;
@@ -90,10 +88,9 @@ private:
void ResetSamplerStates();
void OnSwapChainResized();
void BindFramebuffer(VKFramebuffer* fb);
std::unique_ptr<SwapChain> m_swap_chain;
std::unique_ptr<FrameOutput> m_output;
float m_backbuffer_scale;
// Keep a copy of sampler states to avoid cache lookups every draw
+3 -163
View File
@@ -12,7 +12,6 @@
#include "VideoBackends/Vulkan/VKBoundingBox.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VKPerfQuery.h"
#include "VideoBackends/Vulkan/VKSwapChain.h"
#include "VideoBackends/Vulkan/VKVertexManager.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
@@ -70,170 +69,11 @@ void VideoBackend::InitBackendInfo(const WindowSystemInfo& wsi)
}
}
// Helper method to check whether the Host GPU logging category is enabled.
static bool IsHostGPULoggingEnabled()
{
return Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::LogType::HOST_GPU,
Common::Log::LogLevel::LERROR);
}
// Helper method to determine whether to enable the debug utils extension.
static bool ShouldEnableDebugUtils(bool enable_validation_layers)
{
// Enable debug utils if the Host GPU log option is checked, or validation layers are enabled.
// The only issue here is that if Host GPU is not checked when the instance is created, the debug
// report extension will not be enabled, requiring the game to be restarted before any reports
// will be logged. Otherwise, we'd have to enable debug utils on every instance, when most
// users will never check the Host GPU logging category.
return enable_validation_layers || IsHostGPULoggingEnabled();
}
/* The frontend owns the instance, the device and the window:
* Libretro::Video brings the backend up on the device it negotiated. */
bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
{
if (!LoadVulkanLibrary())
{
PanicAlertFmt("Failed to load Vulkan library.");
return false;
}
// Check for presence of the validation layers before trying to enable it
bool enable_validation_layer = g_Config.bEnableValidationLayer;
if (enable_validation_layer && !VulkanContext::CheckValidationLayerAvailablility())
{
WARN_LOG_FMT(VIDEO, "Validation layer requested but not available, disabling.");
enable_validation_layer = false;
}
// Create Vulkan instance, needed before we can create a surface, or enumerate devices.
// We use this instance to fill in backend info, then re-use it for the actual device.
bool enable_surface = wsi.type != WindowSystemType::Headless;
bool enable_debug_utils = ShouldEnableDebugUtils(enable_validation_layer);
u32 vk_api_version = 0;
VkInstance instance = VulkanContext::CreateVulkanInstance(
wsi.type, enable_debug_utils, enable_validation_layer, &vk_api_version);
if (instance == VK_NULL_HANDLE)
{
PanicAlertFmt("Failed to create Vulkan instance.");
UnloadVulkanLibrary();
return false;
}
// Load instance function pointers.
if (!LoadVulkanInstanceFunctions(instance))
{
PanicAlertFmt("Failed to load Vulkan instance functions.");
vkDestroyInstance(instance, nullptr);
UnloadVulkanLibrary();
return false;
}
// Obtain a list of physical devices (GPUs) from the instance.
// We'll re-use this list later when creating the device.
VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(instance);
if (gpu_list.empty())
{
PanicAlertFmt("No Vulkan physical devices available.");
vkDestroyInstance(instance, nullptr);
UnloadVulkanLibrary();
return false;
}
// Populate BackendInfo with as much information as we can at this point.
VulkanContext::PopulateBackendInfo(&g_backend_info);
VulkanContext::PopulateBackendInfoAdapters(&g_backend_info, gpu_list);
// We need the surface before we can create a device, as some parameters depend on it.
VkSurfaceKHR surface = VK_NULL_HANDLE;
if (enable_surface)
{
surface = SwapChain::CreateVulkanSurface(instance, wsi);
if (surface == VK_NULL_HANDLE)
{
PanicAlertFmt("Failed to create Vulkan surface.");
vkDestroyInstance(instance, nullptr);
UnloadVulkanLibrary();
return false;
}
}
// Since we haven't called InitializeShared yet, iAdapter may be out of range,
// so we have to check it ourselves.
size_t selected_adapter_index = static_cast<size_t>(g_Config.iAdapter);
if (selected_adapter_index >= gpu_list.size())
{
WARN_LOG_FMT(VIDEO, "Vulkan adapter index out of range, selecting first adapter.");
selected_adapter_index = 0;
}
// Now we can create the Vulkan device. VulkanContext takes ownership of the instance and surface.
g_vulkan_context =
VulkanContext::Create(instance, gpu_list[selected_adapter_index], surface, enable_debug_utils,
enable_validation_layer, vk_api_version);
if (!g_vulkan_context)
{
PanicAlertFmt("Failed to create Vulkan device");
UnloadVulkanLibrary();
return false;
}
// Since VulkanContext maintains a copy of the device features and properties, we can use this
// to initialize the backend information, so that we don't need to enumerate everything again.
VulkanContext::PopulateBackendInfoFeatures(&g_backend_info, g_vulkan_context->GetPhysicalDevice(),
g_vulkan_context->GetDeviceInfo());
VulkanContext::PopulateBackendInfoMultisampleModes(
&g_backend_info, g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetDeviceInfo());
g_backend_info.bSupportsExclusiveFullscreen =
enable_surface && g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface);
UpdateActiveConfig();
// Remaining classes are also dependent on object cache.
g_object_cache = std::make_unique<ObjectCache>();
if (!g_object_cache->Initialize())
{
PanicAlertFmt("Failed to initialize Vulkan object cache.");
Shutdown();
return false;
}
// Create swap chain. This has to be done early so that the target size is correct for auto-scale.
std::unique_ptr<SwapChain> swap_chain;
if (surface != VK_NULL_HANDLE)
{
swap_chain = SwapChain::Create(wsi, surface, g_ActiveConfig.bVSyncActive);
if (!swap_chain)
{
PanicAlertFmt("Failed to create Vulkan swap chain.");
Shutdown();
return false;
}
}
// Create command buffers. We do this separately because the other classes depend on it.
g_command_buffer_mgr = std::make_unique<CommandBufferManager>(g_Config.bBackendMultithreading);
size_t swapchain_image_count =
surface != VK_NULL_HANDLE ? swap_chain->GetSwapChainImageCount() : 0;
if (!g_command_buffer_mgr->Initialize(swapchain_image_count))
{
PanicAlertFmt("Failed to create Vulkan command buffers");
Shutdown();
return false;
}
if (!StateTracker::CreateInstance())
{
PanicAlertFmt("Failed to create state tracker");
Shutdown();
return false;
}
auto gfx = std::make_unique<VKGfx>(std::move(swap_chain), wsi.render_surface_scale);
auto vertex_manager = std::make_unique<VertexManager>();
auto perf_query = std::make_unique<PerfQuery>();
auto bounding_box = std::make_unique<VKBoundingBox>();
return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
std::move(bounding_box));
return false;
}
void VideoBackend::Shutdown()
@@ -1,618 +0,0 @@
// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoBackends/Vulkan/VKSwapChain.h"
#include <algorithm>
#include <cstdint>
#include "Common/Assert.h"
#include "Common/Contains.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/VKTexture.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoCommon/Present.h"
#if defined(VK_USE_PLATFORM_XLIB_KHR)
#include <X11/Xlib.h>
#endif
namespace Vulkan
{
SwapChain::SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync)
: m_wsi(wsi), m_surface(surface), m_vsync_enabled(vsync),
m_fullscreen_supported(g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface))
{
}
SwapChain::~SwapChain()
{
DestroySwapChainImages();
DestroySwapChain();
DestroySurface();
}
VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi)
{
#if defined(VK_USE_PLATFORM_WIN32_KHR)
if (wsi.type == WindowSystemType::Windows)
{
VkWin32SurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkWin32SurfaceCreateFlagsKHR flags
nullptr, // HINSTANCE hinstance
static_cast<HWND>(wsi.render_surface) // HWND hwnd
};
VkSurfaceKHR surface;
VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
#if defined(VK_USE_PLATFORM_XLIB_KHR)
if (wsi.type == WindowSystemType::X11)
{
VkXlibSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkXlibSurfaceCreateFlagsKHR flags
static_cast<Display*>(wsi.display_connection), // Display* dpy
reinterpret_cast<Window>(wsi.render_surface) // Window window
};
VkSurfaceKHR surface;
VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
if (wsi.type == WindowSystemType::Android)
{
VkAndroidSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkAndroidSurfaceCreateFlagsKHR flags
reinterpret_cast<ANativeWindow*>(wsi.render_surface) // ANativeWindow* window
};
VkSurfaceKHR surface;
VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
#if defined(VK_USE_PLATFORM_METAL_EXT)
if (wsi.type == WindowSystemType::MacOS)
{
VkMetalSurfaceCreateInfoEXT surface_create_info = {
VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0,
static_cast<const CAMetalLayer*>(wsi.render_surface)};
VkSurfaceKHR surface;
VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
return VK_NULL_HANDLE;
}
std::unique_ptr<SwapChain> SwapChain::Create(const WindowSystemInfo& wsi, VkSurfaceKHR surface,
bool vsync)
{
std::unique_ptr<SwapChain> swap_chain = std::make_unique<SwapChain>(wsi, surface, vsync);
if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages())
return nullptr;
return swap_chain;
}
bool SwapChain::SelectSurfaceFormat()
{
u32 format_count;
VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(),
m_surface, &format_count, nullptr);
if (res != VK_SUCCESS || format_count == 0)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
return false;
}
std::vector<VkSurfaceFormatKHR> surface_formats(format_count);
res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface,
&format_count, surface_formats.data());
ASSERT(res == VK_SUCCESS);
// If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA8
if (surface_formats[0].format == VK_FORMAT_UNDEFINED)
{
m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM;
m_surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
return true;
}
const VkSurfaceFormatKHR* surface_format_RGBA8 = nullptr;
const VkSurfaceFormatKHR* surface_format_BGRA8 = nullptr;
const VkSurfaceFormatKHR* surface_format_RGB10_A2 = nullptr;
const VkSurfaceFormatKHR* surface_format_RGBA16F_scRGB = nullptr;
// Try to find all suitable formats.
for (const VkSurfaceFormatKHR& surface_format : surface_formats)
{
// Some drivers seem to return a RGBA8 SRGB format here (Intel Mesa).
// Some other drivers return both a RGBA8 SRGB and UNORM formats (Nvidia).
// This results in gamma correction when presenting to the screen, which we don't want,
// because we already apply gamma ourselves, and we might not use sRGB gamma.
// Force using a linear format instead, if this is the case.
VkFormat format = VKTexture::GetLinearFormat(surface_format.format);
if (surface_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
{
if (format == VK_FORMAT_R8G8B8A8_UNORM)
surface_format_RGBA8 = &surface_format;
else if (format == VK_FORMAT_B8G8R8A8_UNORM)
surface_format_BGRA8 = &surface_format;
else if (format == VK_FORMAT_A2B10G10R10_UNORM_PACK32)
surface_format_RGB10_A2 = &surface_format;
}
else if (format == VK_FORMAT_R16G16B16A16_SFLOAT &&
surface_format.colorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT)
{
surface_format_RGBA16F_scRGB = &surface_format;
}
}
const VkSurfaceFormatKHR* surface_format = nullptr;
// Pick the best format.
// "g_ActiveConfig" might not have been been updated yet.
if (g_Config.bHDR && surface_format_RGBA16F_scRGB)
surface_format = surface_format_RGBA16F_scRGB;
else if (surface_format_RGB10_A2)
surface_format = surface_format_RGB10_A2;
else if (surface_format_RGBA8)
surface_format = surface_format_RGBA8;
else if (surface_format_BGRA8)
surface_format = surface_format_BGRA8;
if (surface_format)
{
const VkFormat format = VKTexture::GetLinearFormat(surface_format->format);
if (format == VK_FORMAT_R8G8B8A8_UNORM)
m_texture_format = AbstractTextureFormat::RGBA8;
else if (format == VK_FORMAT_B8G8R8A8_UNORM)
m_texture_format = AbstractTextureFormat::BGRA8;
else if (format == VK_FORMAT_A2B10G10R10_UNORM_PACK32)
m_texture_format = AbstractTextureFormat::RGB10_A2;
else if (format == VK_FORMAT_R16G16B16A16_SFLOAT)
m_texture_format = AbstractTextureFormat::RGBA16F;
m_surface_format.format = format;
m_surface_format.colorSpace = surface_format->colorSpace;
return true;
}
PanicAlertFmt("Failed to find a suitable format for swap chain buffers.");
return false;
}
bool SwapChain::SelectPresentMode()
{
VkResult res;
u32 mode_count;
res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface,
&mode_count, nullptr);
if (res != VK_SUCCESS || mode_count == 0)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
return false;
}
std::vector<VkPresentModeKHR> present_modes(mode_count);
res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface,
&mode_count, present_modes.data());
ASSERT(res == VK_SUCCESS);
// If vsync is enabled, use VK_PRESENT_MODE_FIFO_KHR.
// This check should not fail with conforming drivers, as the FIFO present mode is mandated by
// the specification (VK_KHR_swapchain). In case it isn't though, fall through to any other mode.
if (m_vsync_enabled && Common::Contains(present_modes, VK_PRESENT_MODE_FIFO_KHR))
{
m_present_mode = VK_PRESENT_MODE_FIFO_KHR;
return true;
}
// Prefer screen-tearing, if possible, for lowest latency.
if (Common::Contains(present_modes, VK_PRESENT_MODE_IMMEDIATE_KHR))
{
m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
return true;
}
// Use optimized-vsync above vsync.
if (Common::Contains(present_modes, VK_PRESENT_MODE_MAILBOX_KHR))
{
m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
return true;
}
// Fall back to whatever is available.
m_present_mode = present_modes[0];
return true;
}
bool SwapChain::CreateSwapChain()
{
// Look up surface properties to determine image count and dimensions
VkSurfaceCapabilitiesKHR surface_capabilities;
VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_vulkan_context->GetPhysicalDevice(),
m_surface, &surface_capabilities);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ");
return false;
}
// Select swap chain format and present mode
if (!SelectSurfaceFormat() || !SelectPresentMode())
return false;
// Select number of images in swap chain, we prefer one buffer in the background to work on
uint32_t image_count = surface_capabilities.minImageCount + 1;
// maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers.
if (surface_capabilities.maxImageCount > 0)
image_count = std::min(image_count, surface_capabilities.maxImageCount);
// Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
// determines window size?
VkExtent2D size = surface_capabilities.currentExtent;
if (size.width == UINT32_MAX)
{
size.width = std::max(g_presenter->GetBackbufferWidth(), 1);
size.height = std::max(g_presenter->GetBackbufferHeight(), 1);
}
size.width = std::clamp(size.width, surface_capabilities.minImageExtent.width,
surface_capabilities.maxImageExtent.width);
size.height = std::clamp(size.height, surface_capabilities.minImageExtent.height,
surface_capabilities.maxImageExtent.height);
// Prefer identity transform if possible
VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR))
transform = surface_capabilities.currentTransform;
// Select swap chain flags, we only need a colour attachment
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT))
{
ERROR_LOG_FMT(VIDEO, "Vulkan: Swap chain does not support usage as color attachment");
return false;
}
// Select the number of image layers for Quad-Buffered stereoscopy
uint32_t image_layers = g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer ? 2 : 1;
// Store the old/current swap chain when recreating for resize
VkSwapchainKHR old_swap_chain = m_swap_chain;
m_swap_chain = VK_NULL_HANDLE;
// Now we can actually create the swap chain
VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
nullptr,
0,
m_surface,
image_count,
m_surface_format.format,
m_surface_format.colorSpace,
size,
image_layers,
image_usage,
VK_SHARING_MODE_EXCLUSIVE,
0,
nullptr,
transform,
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
m_present_mode,
VK_TRUE,
old_swap_chain};
std::array<uint32_t, 2> indices = {{
g_vulkan_context->GetGraphicsQueueFamilyIndex(),
g_vulkan_context->GetPresentQueueFamilyIndex(),
}};
if (g_vulkan_context->GetGraphicsQueueFamilyIndex() !=
g_vulkan_context->GetPresentQueueFamilyIndex())
{
swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swap_chain_info.queueFamilyIndexCount = 2;
swap_chain_info.pQueueFamilyIndices = indices.data();
}
#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN
if (m_fullscreen_supported)
{
VkSurfaceFullScreenExclusiveInfoEXT fullscreen_support = {};
swap_chain_info.pNext = &fullscreen_support;
fullscreen_support.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT;
fullscreen_support.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT;
auto platform_info = g_vulkan_context->GetPlatformExclusiveFullscreenInfo(m_wsi);
fullscreen_support.pNext = &platform_info;
res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr,
&m_swap_chain);
if (res != VK_SUCCESS)
{
// Try without exclusive fullscreen.
WARN_LOG_FMT(VIDEO, "Failed to create exclusive fullscreen swapchain, trying without.");
swap_chain_info.pNext = nullptr;
g_backend_info.bSupportsExclusiveFullscreen = false;
m_fullscreen_supported = false;
}
}
#endif
if (m_swap_chain == VK_NULL_HANDLE)
{
res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr,
&m_swap_chain);
}
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: ");
return false;
}
// Now destroy the old swap chain, since it's been recreated.
// We can do this immediately since all work should have been completed before calling resize.
if (old_swap_chain != VK_NULL_HANDLE)
vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), old_swap_chain, nullptr);
m_width = size.width;
m_height = size.height;
m_layers = image_layers;
return true;
}
bool SwapChain::SetupSwapChainImages()
{
ASSERT(m_swap_chain_images.empty());
uint32_t image_count;
VkResult res =
vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, nullptr);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: ");
return false;
}
std::vector<VkImage> images(image_count);
res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count,
images.data());
ASSERT(res == VK_SUCCESS);
const TextureConfig texture_config(
TextureConfig(m_width, m_height, 1, m_layers, 1, m_texture_format,
AbstractTextureFlag_RenderTarget, AbstractTextureType::Texture_2DArray));
const VkRenderPass load_render_pass = g_object_cache->GetRenderPass(
m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_LOAD);
const VkRenderPass clear_render_pass = g_object_cache->GetRenderPass(
m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_CLEAR);
if (load_render_pass == VK_NULL_HANDLE || clear_render_pass == VK_NULL_HANDLE)
{
PanicAlertFmt("Failed to get swap chain render passes.");
return false;
}
m_swap_chain_images.reserve(image_count);
for (uint32_t i = 0; i < image_count; i++)
{
SwapChainImage image;
image.image = images[i];
// Create texture object, which creates a view of the backbuffer
image.texture =
VKTexture::CreateAdopted(texture_config, image.image,
m_layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D,
VK_IMAGE_LAYOUT_UNDEFINED);
if (!image.texture)
return false;
image.framebuffer = VKFramebuffer::Create(image.texture.get(), nullptr, {});
if (!image.framebuffer)
{
image.texture.reset();
return false;
}
m_swap_chain_images.emplace_back(std::move(image));
}
return true;
}
void SwapChain::DestroySwapChainImages()
{
for (auto& it : m_swap_chain_images)
{
// Images themselves are cleaned up by the swap chain object
it.framebuffer.reset();
it.texture.reset();
}
m_swap_chain_images.clear();
}
void SwapChain::DestroySwapChain()
{
if (m_swap_chain == VK_NULL_HANDLE)
return;
// Release exclusive fullscreen before destroying.
if (m_current_fullscreen_state)
SetFullscreenState(false);
vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), m_swap_chain, nullptr);
m_swap_chain = VK_NULL_HANDLE;
}
VkResult SwapChain::AcquireNextImage()
{
VkResult res = vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX,
g_command_buffer_mgr->GetCurrentCommandBufferSemaphore(),
VK_NULL_HANDLE, &m_current_swap_chain_image_index);
m_current_swap_chain_image_is_valid = res >= 0;
if (IsCurrentImageValid())
g_command_buffer_mgr->MarkCurrentCommandBufferSemaphoreUsed();
if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR)
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR failed: ");
return res;
}
bool SwapChain::ResizeSwapChain()
{
DestroySwapChainImages();
if (!CreateSwapChain() || !SetupSwapChainImages())
{
PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)");
return false;
}
return true;
}
bool SwapChain::RecreateSwapChain()
{
DestroySwapChainImages();
DestroySwapChain();
if (!CreateSwapChain() || !SetupSwapChainImages())
{
PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)");
return false;
}
return true;
}
bool SwapChain::SetVSync(bool enabled)
{
if (m_vsync_enabled == enabled)
return true;
// Recreate the swap chain with the new present mode.
m_vsync_enabled = enabled;
return RecreateSwapChain();
}
bool SwapChain::SetFullscreenState(bool state)
{
#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN
if (m_current_fullscreen_state == state)
return true;
if (state)
{
VkResult res = vkAcquireFullScreenExclusiveModeEXT(g_vulkan_context->GetDevice(), m_swap_chain);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkAcquireFullScreenExclusiveModeEXT failed:");
return false;
}
INFO_LOG_FMT(VIDEO, "Exclusive fullscreen acquired.");
}
else
{
VkResult res = vkReleaseFullScreenExclusiveModeEXT(g_vulkan_context->GetDevice(), m_swap_chain);
if (res != VK_SUCCESS)
LOG_VULKAN_ERROR(res, "vkReleaseFullScreenExclusiveModeEXT failed:");
INFO_LOG_FMT(VIDEO, "Exclusive fullscreen released.");
}
m_current_fullscreen_state = state;
return true;
#else
return false;
#endif
}
bool SwapChain::RecreateSurface(void* native_handle)
{
// Destroy the old swap chain, images, and surface.
DestroySwapChainImages();
DestroySwapChain();
DestroySurface();
// Re-create the surface with the new native handle
m_wsi.render_surface = native_handle;
m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_wsi);
if (m_surface == VK_NULL_HANDLE)
return false;
// The validation layers get angry at us if we don't call this before creating the swapchain.
VkBool32 present_supported = VK_TRUE;
VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(
g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetPresentQueueFamilyIndex(),
m_surface, &present_supported);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
return false;
}
if (!present_supported)
{
PanicAlertFmt("Recreated surface does not support presenting.");
return false;
}
// Update exclusive fullscreen support (unlikely to change).
m_fullscreen_supported = g_vulkan_context->SupportsExclusiveFullscreen(m_wsi, m_surface);
g_backend_info.bSupportsExclusiveFullscreen = m_fullscreen_supported;
m_current_fullscreen_state = false;
m_next_fullscreen_state = false;
// Finally re-create the swap chain
if (!CreateSwapChain() || !SetupSwapChainImages())
return false;
return true;
}
void SwapChain::DestroySurface()
{
vkDestroySurfaceKHR(g_vulkan_context->GetVulkanInstance(), m_surface, nullptr);
m_surface = VK_NULL_HANDLE;
}
} // namespace Vulkan
@@ -1,121 +0,0 @@
// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/WindowSystemInfo.h"
#include "VideoBackends/Vulkan/Constants.h"
#include "VideoCommon/TextureConfig.h"
namespace Vulkan
{
class CommandBufferManager;
class ObjectCache;
class VKTexture;
class VKFramebuffer;
class SwapChain
{
public:
SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync);
~SwapChain();
// Creates a vulkan-renderable surface for the specified window handle.
static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi);
// Create a new swap chain from a pre-existing surface.
static std::unique_ptr<SwapChain> Create(const WindowSystemInfo& wsi, VkSurfaceKHR surface,
bool vsync);
VkSurfaceKHR GetSurface() const { return m_surface; }
VkSurfaceFormatKHR GetSurfaceFormat() const { return m_surface_format; }
AbstractTextureFormat GetTextureFormat() const { return m_texture_format; }
VkSwapchainKHR GetSwapChain() const { return m_swap_chain; }
u32 GetWidth() const { return m_width; }
u32 GetHeight() const { return m_height; }
u32 GetCurrentImageIndex() const { return m_current_swap_chain_image_index; }
bool IsCurrentImageValid() const { return m_current_swap_chain_image_is_valid; }
size_t GetSwapChainImageCount() const { return m_swap_chain_images.size(); }
VkImage GetCurrentImage() const
{
return m_swap_chain_images[m_current_swap_chain_image_index].image;
}
VKTexture* GetCurrentTexture() const
{
const size_t idx = static_cast<size_t>(m_current_swap_chain_image_index);
if (idx >= m_swap_chain_images.size() || m_swap_chain_images.empty())
return nullptr;
return m_swap_chain_images[m_current_swap_chain_image_index].texture.get();
}
VKFramebuffer* GetCurrentFramebuffer() const
{
const size_t idx = static_cast<size_t>(m_current_swap_chain_image_index);
const size_t count = m_swap_chain_images.size();
if (count == 0 || idx >= count)
return nullptr;
return m_swap_chain_images[m_current_swap_chain_image_index].framebuffer.get();
}
VkResult AcquireNextImage();
bool RecreateSurface(void* native_handle);
bool ResizeSwapChain();
bool RecreateSwapChain();
// Change vsync enabled state. This may fail as it causes a swapchain recreation.
bool SetVSync(bool enabled);
// Is exclusive fullscreen supported?
bool IsFullscreenSupported() const { return m_fullscreen_supported; }
// Retrieves the "next" fullscreen state. Safe to call off-thread.
bool GetCurrentFullscreenState() const { return m_current_fullscreen_state; }
bool GetNextFullscreenState() const { return m_next_fullscreen_state; }
void SetNextFullscreenState(bool state) { m_next_fullscreen_state = state; }
// Updates the fullscreen state. Must call on-thread.
bool SetFullscreenState(bool state);
private:
bool SelectSurfaceFormat();
bool SelectPresentMode();
bool CreateSwapChain();
void DestroySwapChain();
bool SetupSwapChainImages();
void DestroySwapChainImages();
void DestroySurface();
struct SwapChainImage
{
VkImage image{};
std::unique_ptr<VKTexture> texture;
std::unique_ptr<VKFramebuffer> framebuffer;
};
WindowSystemInfo m_wsi;
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
VkSurfaceFormatKHR m_surface_format = {};
VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
AbstractTextureFormat m_texture_format = AbstractTextureFormat::Undefined;
bool m_vsync_enabled = false;
bool m_fullscreen_supported = false;
bool m_current_fullscreen_state = false;
bool m_next_fullscreen_state = false;
bool m_current_swap_chain_image_is_valid = false;
VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE;
std::vector<SwapChainImage> m_swap_chain_images;
u32 m_current_swap_chain_image_index = 0;
u32 m_width = 0;
u32 m_height = 0;
u32 m_layers = 0;
};
} // namespace Vulkan
@@ -651,12 +651,6 @@ bool VulkanContext::SelectDeviceExtensions(bool enable_surface)
if (enable_surface && !AddExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true))
return false;
#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN
// VK_EXT_full_screen_exclusive
if (AddExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, true))
INFO_LOG_FMT(VIDEO, "Using VK_EXT_full_screen_exclusive for exclusive fullscreen.");
#endif
AddExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false);
AddExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, false);
AddExtension("VK_KHR_portability_subset", false);
@@ -1135,53 +1129,4 @@ void VulkanContext::InitDriverDetails()
DriverDetails::Family::UNKNOWN, std::move(device_name));
}
bool VulkanContext::SupportsExclusiveFullscreen(const WindowSystemInfo& wsi, VkSurfaceKHR surface)
{
#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN
if (!surface || !vkGetPhysicalDeviceSurfaceCapabilities2KHR ||
!SupportsDeviceExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME))
{
return false;
}
VkPhysicalDeviceSurfaceInfo2KHR si = {};
si.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR;
si.surface = surface;
auto platform_info = GetPlatformExclusiveFullscreenInfo(wsi);
si.pNext = &platform_info;
VkSurfaceCapabilities2KHR caps = {};
caps.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR;
VkSurfaceCapabilitiesFullScreenExclusiveEXT fullscreen_caps = {};
fullscreen_caps.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT;
fullscreen_caps.fullScreenExclusiveSupported = VK_TRUE;
caps.pNext = &fullscreen_caps;
VkResult res = vkGetPhysicalDeviceSurfaceCapabilities2KHR(m_physical_device, &si, &caps);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilities2KHR failed:");
return false;
}
return fullscreen_caps.fullScreenExclusiveSupported;
#else
return false;
#endif
}
#ifdef WIN32
VkSurfaceFullScreenExclusiveWin32InfoEXT
VulkanContext::GetPlatformExclusiveFullscreenInfo(const WindowSystemInfo& wsi)
{
VkSurfaceFullScreenExclusiveWin32InfoEXT info = {};
info.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT;
info.hmonitor =
MonitorFromWindow(static_cast<HWND>(wsi.render_surface), MONITOR_DEFAULTTOPRIMARY);
return info;
}
#endif
} // namespace Vulkan
@@ -120,17 +120,8 @@ public:
// Returns true if the specified extension is supported and enabled.
bool SupportsDeviceExtension(const char* name) const;
// Returns true if exclusive fullscreen is supported for the given surface.
bool SupportsExclusiveFullscreen(const WindowSystemInfo& wsi, VkSurfaceKHR surface);
VmaAllocator GetMemoryAllocator() const { return m_allocator; }
#ifdef WIN32
// Returns the platform-specific exclusive fullscreen structure.
VkSurfaceFullScreenExclusiveWin32InfoEXT
GetPlatformExclusiveFullscreenInfo(const WindowSystemInfo& wsi);
#endif
private:
static bool SelectInstanceExtensions(std::vector<const char*>* extension_list,
WindowSystemType wstype, bool enable_debug_utils,
@@ -28,11 +28,6 @@
#include <unistd.h>
#endif
// Currently, exclusive fullscreen is only supported on Windows.
#if defined(WIN32)
#define SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN 1
#endif
// We abuse the preprocessor here to only need to specify function names once.
#define VULKAN_MODULE_ENTRY_POINT(name, required) extern PFN_##name name;
#define VULKAN_INSTANCE_ENTRY_POINT(name, required) extern PFN_##name name;
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Common/CommonTypes.h"
#include "VideoCommon/TextureConfig.h"
class AbstractFramebuffer;
/* Where a backend draws the picture it presents, and what hands that
* picture to whoever shows it: the frontend owns the window, so this is
* a render target the frontend reads in place, not a swap chain. */
class FrameOutput
{
public:
virtual ~FrameOutput() {}
/* The framebuffer this frame's picture goes into, once whoever shows
* the pictures is done with what it last read from it. */
virtual AbstractFramebuffer* Acquire() = 0;
/* The picture is complete: hands it on. */
virtual void Present() = 0;
virtual u32 GetWidth() const = 0;
virtual u32 GetHeight() const = 0;
virtual AbstractTextureFormat GetFormat() const = 0;
};