Files
parallel-gx1/Source/Core/DolphinLibretro/Video.cpp
T
LibretroAdmin 065c9c3bea Vulkan: the XFB texture handed to the frontend in place
As on Direct3D 11, a picture whose draw would be a plain copy is not
drawn: the XFB image is handed over with set_image and the frontend
reads it where it is. It goes over in shader-read layout, submitted on
the core's thread before the frontend is told, through a 2D view of its
one layer made for that picture: the frontend's shaders read a 2D
image, and a view is not kept for the next picture, as an image's
handle can come back for another image once it is destroyed. The view
goes once the core's next submission completes, which follows every
frontend frame that read it.

The image is not one of the output's, so no sync index is waited on.
The frontend keeps reading it in the frames that repeat it, so before
each repeat the output puts it back in shader-read layout if the core
used it in another one meanwhile, a copy's source say: FrameOutput gains
Repeat(), called at the end of a run that reports a repeat.
2026-11-06 20:39:04 +01:00

872 lines
28 KiB
C++

// needs to be first
#ifdef HAS_VULKAN
#include "VideoBackends/Vulkan/VulkanLoader.h"
#endif
#include "DolphinLibretro/Video.h"
#include "DolphinLibretro/Output.h"
#include <cassert>
#include <memory>
#include <sstream>
#include <vector>
#ifdef _WIN32
#include "Common/DynamicLibrary.h"
#include "VideoBackends/D3D/VideoBackend.h"
#include "VideoBackends/D3D/D3DGfx.h"
#include "VideoBackends/D3D/D3DVertexManager.h"
#include "VideoBackends/D3D/D3DPerfQuery.h"
#include "VideoBackends/D3D/D3DBoundingBox.h"
#include "VideoBackends/D3D12/Common.h"
#include "VideoBackends/D3D12/VideoBackend.h"
#include "VideoBackends/D3D12/D3D12Gfx.h"
#include "VideoBackends/D3D12/D3D12VertexManager.h"
#include "VideoBackends/D3D12/D3D12PerfQuery.h"
#include "VideoBackends/D3D12/D3D12BoundingBox.h"
#endif
#ifdef HAS_OPENGL
#include "Common/GL/GLContext.h"
#include "Common/GL/GLUtil.h"
#include "VideoBackends/OGL/OGLGfx.h"
#endif
#include "Common/Logging/Log.h"
#include "Common/Version.h"
#include "Core/Config/GraphicsSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/VideoInterface.h"
#include "Core/Host.h"
#include "Core/System.h"
#include "DolphinLibretro/Common/Options.h"
#include "DolphinLibretro/VR.h"
#include "DolphinLibretro/VideoContexts/ContextStatus.h"
#include "VideoCommon/AsyncRequests.h"
#include "VideoCommon/Fifo.h"
#include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
#ifdef HAS_VULKAN
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VKVertexManager.h"
#include "VideoBackends/Vulkan/VKPerfQuery.h"
#include "VideoBackends/Vulkan/VKBoundingBox.h"
#endif
namespace Libretro
{
extern std::vector<std::string> g_normalized_game_paths;
namespace Video
{
#ifdef _WIN32
static Common::DynamicLibrary d3d11_library;
static Common::DynamicLibrary d3d12_library;
/* Direct3D 11 version 2: the interface, and the levels of its context
* lock this thread holds */
static retro_hw_render_interface_d3d11* s_d3d11_v2;
static unsigned s_d3d11_depth;
#endif
int GetAdjustedBaseWidth()
{
// VR packs both eyes side by side, each at the full width.
return VR::IsEnabled() ? EFB_WIDTH * 2 : EFB_WIDTH;
}
int GetAdjustedBaseHeight()
{
const bool crop_overscan = Libretro::Options::GetCached<bool>(
Libretro::Options::gfx_settings::CROP_OVERSCAN);
if (crop_overscan)
{
// A 480-line signal (native NTSC, or a PAL game that switched to PAL60/EuRGB60 at runtime)
// should report 480 so the frontend / CRT switchres picks a 480i@60 mode instead of stretching
// a 576-line PAL mode down to ~52 Hz. Detect the 60 Hz output from the current refresh rate,
// since the cartridge region stays PAL even after the in-game 60 Hz switch.
const bool is_480_line_output =
retro_get_region() == RETRO_REGION_NTSC ||
Core::System::GetInstance().GetVideoInterface().GetTargetRefreshRate() > 55.0;
return is_480_line_output ? 480 : 576;
}
return EFB_HEIGHT;
}
void Init()
{
DEBUG_LOG_FMT(VIDEO, "Video - Init");
std::string renderer = Libretro::Options::GetCached<std::string>(
Libretro::Options::gfx_settings::RENDERER);
if (renderer == "Hardware")
{
retro_hw_context_type preferred = RETRO_HW_CONTEXT_NONE;
if (environ_cb(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred))
{
switch (preferred)
{
#ifdef HAS_OPENGL
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
if (SetHWRender(preferred, 3, 2))
return;
if (SetHWRender(preferred, 3, 1))
return;
break;
#endif
default:
if (SetHWRender(preferred))
return;
}
}
#if defined(HAS_OPENGL) && !defined(ANDROID) && !defined(__WEBOS__)
if (SetHWRender(RETRO_HW_CONTEXT_OPENGL_CORE))
return;
if (SetHWRender(RETRO_HW_CONTEXT_OPENGL))
return;
#endif
#ifdef HAS_OPENGL
if (SetHWRender(RETRO_HW_CONTEXT_OPENGLES_VERSION, 3, 2))
return;
if (SetHWRender(RETRO_HW_CONTEXT_OPENGLES_VERSION, 3, 1))
return;
if (SetHWRender(RETRO_HW_CONTEXT_OPENGLES3))
return;
#endif
#ifdef _WIN32
if (SetHWRender(RETRO_HW_CONTEXT_D3D12))
return;
if (SetHWRender(RETRO_HW_CONTEXT_D3D11))
return;
#endif
#ifdef HAS_VULKAN
if (SetHWRender(RETRO_HW_CONTEXT_VULKAN))
return;
#endif
}
hw_render.context_type = RETRO_HW_CONTEXT_NONE;
if (renderer == "Software")
Config::SetBase(Config::MAIN_GFX_BACKEND, "Software Renderer");
else
Config::SetBase(Config::MAIN_GFX_BACKEND, "Null");
}
void D3D11Lock(void)
{
#ifdef _WIN32
bool used;
if (!s_d3d11_v2)
return;
used = s_d3d11_v2->lock_context(s_d3d11_v2->handle);
/* the frontend had the context: everything bound goes back */
if (s_d3d11_depth++ == 0 && used && g_gfx && DX11::D3D::stateman)
static_cast<DX11::Gfx*>(g_gfx.get())->RestoreContext();
#endif
}
void D3D11Unlock(void)
{
#ifdef _WIN32
if (!s_d3d11_v2 || !s_d3d11_depth)
return;
s_d3d11_depth--;
s_d3d11_v2->unlock_context(s_d3d11_v2->handle);
#endif
}
unsigned D3D11Suspend(void)
{
unsigned depth = 0;
#ifdef _WIN32
depth = s_d3d11_depth;
while (s_d3d11_depth)
D3D11Unlock();
#endif
return depth;
}
void D3D11Resume(unsigned depth)
{
while (depth--)
D3D11Lock();
}
bool SetHWRender(retro_hw_context_type type, const int version_major, const int version_minor)
{
DEBUG_LOG_FMT(VIDEO, "Video - SetHWRender!");
hw_render.context_type = type;
hw_render.context_reset = ContextReset;
hw_render.context_destroy = ContextDestroy;
hw_render.bottom_left_origin = true;
bool success = false;
switch (type)
{
#ifdef HAS_OPENGL
case RETRO_HW_CONTEXT_OPENGL_CORE:
// minimum requirements to run is opengl 3.3 (RA will try to use highest version available anyway)
hw_render.version_major = (version_major != -1) ? version_major : 3;
hw_render.version_minor = (version_minor != -1) ? version_minor : 3;
if (environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
{
Config::SetBase(Config::MAIN_GFX_BACKEND, "OGL");
success = true;
} else {
WARN_LOG_FMT(VIDEO, "Video - SetHWRender - failed to set hw renderer for OpenGL Core");
}
break;
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
case RETRO_HW_CONTEXT_OPENGLES3:
case RETRO_HW_CONTEXT_OPENGL:
{
// when using RETRO_HW_CONTEXT_OPENGL you can't set version above 3.0 (RA will try to use highest version available anyway)
// dolphin support OpenGL ES 3.0 too (no support for 2.0) so we are good
hw_render.version_major = (version_major != -1) ? version_major : 3;
hw_render.version_minor = (version_minor != -1) ? version_minor : 0;
std::string api_name =
(type == RETRO_HW_CONTEXT_OPENGLES3 || type == RETRO_HW_CONTEXT_OPENGLES_VERSION)
? "OpenGL ES " + std::to_string(hw_render.version_major) + "." +
std::to_string(hw_render.version_minor)
: "OpenGL " + std::to_string(hw_render.version_major) + "." +
std::to_string(hw_render.version_minor);
if (environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
{
// Shared context is required with "gl" video driver
if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT, nullptr)) {
WARN_LOG_FMT(VIDEO, "Video - SetHWRender - unable to set shared context for {}", api_name);
}
INFO_LOG_FMT(VIDEO, "Video - SetHWRender - using {}", api_name);
Config::SetBase(Config::MAIN_GFX_BACKEND, "OGL");
success = true;
}
else
{
WARN_LOG_FMT(VIDEO, "Video - SetHWRender - failed to set hw renderer for {}", api_name);
}
break;
}
#endif
#ifdef _WIN32
case RETRO_HW_CONTEXT_D3D11:
hw_render.version_major = 11;
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, from whichever thread presents */
static const struct retro_hw_render_context_negotiation_interface_d3d11 negotiation = {
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11,
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11_VERSION,
RETRO_HW_RENDER_INTERFACE_D3D11_VERSION_2};
struct retro_hw_render_context_negotiation_interface probe;
probe.interface_type = RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11;
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, "D3D");
success = true;
}
break;
case RETRO_HW_CONTEXT_D3D12:
hw_render.version_major = 12;
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;
}
break;
#endif
#ifdef HAS_VULKAN
case RETRO_HW_CONTEXT_VULKAN:
hw_render.version_major = VK_API_VERSION_1_0;
hw_render.version_minor = 0;
if (environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
{
static const struct retro_hw_render_context_negotiation_interface_vulkan iface = {
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN,
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION,
Vk::GetApplicationInfo,
Vk::CreateDevice,
NULL, // destroy_device
#ifdef __APPLE__
Vk::CreateInstance, // create_instance (v2 API)
#else
NULL, // create_instance
#endif
Vk::CreateDevice2, // create_device2 (v2 API)
};
environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, (void*)&iface);
Config::SetBase(Config::MAIN_GFX_BACKEND, "Vulkan");
success = true;
}
break;
#endif
default:
break;
}
return success;
}
#ifdef _WIN32
/* The Direct3D 11 backend on the frontend's device, with the context
* lock held. */
static void ContextResetD3D11(retro_hw_render_interface_d3d11* d3d, const WindowSystemInfo& wsi,
int efbScale)
{
DX11::D3D::device = d3d->device;
DX11::D3D::context = d3d->context;
DX11::D3D::feature_level = d3d->featureLevel;
if (!d3d11_library.IsOpen() && !d3d11_library.Open("d3d11.dll"))
{
d3d11_library.Close();
ERROR_LOG_FMT(VIDEO, "Failed to load D3D11 Libraries");
return;
}
if (!D3DCommon::LoadLibraries())
{
ERROR_LOG_FMT(VIDEO, "Failed to load the dxgi library");
return;
}
if (FAILED(DX11::D3D::device.As(&DX11::D3D::device1)))
{
WARN_LOG_FMT(VIDEO, "Missing Direct3D 11.1 support. Logical operations will not be supported.");
g_backend_info.bSupportsLogicOp = false;
}
DX11::D3D::stateman = std::make_unique<DX11::D3D::StateManager>();
DX11::VideoBackend* d3d11 = static_cast<DX11::VideoBackend*>(g_video_backend);
if (!d3d11)
{
WARN_LOG_FMT(VIDEO, "D3D11 backend is null");
return;
}
d3d11->FillD3DBackendInfo();
UpdateActiveConfig();
std::unique_ptr<DX11Output> output = std::make_unique<DX11Output>(
s_d3d11_v2, GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale);
if (!output->Create())
return;
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>();
if(d3d11->InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
std::move(bounding_box)))
g_context_status.MarkInitialized();
}
#endif
void ContextReset(void)
{
DEBUG_LOG_FMT(VIDEO, "Video - ContextReset!");
g_context_status.MarkReset();
#ifdef HAS_VULKAN
if (hw_render.context_type == RETRO_HW_CONTEXT_VULKAN)
{
retro_hw_render_interface* vulkan;
if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void**)&vulkan) ||
!vulkan)
{
ERROR_LOG_FMT(VIDEO, "Failed to get HW rendering interface!");
return;
}
if (vulkan->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION)
{
ERROR_LOG_FMT(VIDEO, "HW render interface mismatch, expected {}, got {}",
RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION, vulkan->interface_version);
return;
}
Vk::SetHWRenderInterface(vulkan);
}
#endif
#ifdef _WIN32
if (hw_render.context_type == RETRO_HW_CONTEXT_D3D11)
{
WindowSystemInfo wsi(WindowSystemType::Libretro, nullptr, nullptr, nullptr);
int efbScale = Libretro::Options::GetCached<int>(
Libretro::Options::gfx_settings::EFB_SCALE, 1);
wsi.render_surface_scale = efbScale;
g_video_backend->PrepareWindow(wsi);
retro_hw_render_interface_d3d11* d3d;
if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void**)&d3d) || !d3d)
{
ERROR_LOG_FMT(VIDEO, "Failed to get HW rendering interface!");
return;
}
if (d3d->interface_version != RETRO_HW_RENDER_INTERFACE_D3D11_VERSION &&
d3d->interface_version != RETRO_HW_RENDER_INTERFACE_D3D11_VERSION_2)
{
ERROR_LOG_FMT(VIDEO, "HW render interface mismatch, expected {} or {}, got {}!",
RETRO_HW_RENDER_INTERFACE_D3D11_VERSION,
RETRO_HW_RENDER_INTERFACE_D3D11_VERSION_2, d3d->interface_version);
return;
}
s_d3d11_v2 = d3d->interface_version >= RETRO_HW_RENDER_INTERFACE_D3D11_VERSION_2 ? d3d : nullptr;
s_d3d11_depth = 0;
D3D11Lock();
ContextResetD3D11(d3d, wsi, efbScale);
D3D11Unlock();
return;
}
if (hw_render.context_type == RETRO_HW_CONTEXT_D3D12)
{
WindowSystemInfo wsi(WindowSystemType::Libretro, nullptr, nullptr, nullptr);
int efbScale = Libretro::Options::GetCached<int>(
Libretro::Options::gfx_settings::EFB_SCALE, 1);
wsi.render_surface_scale = efbScale;
retro_hw_render_interface_d3d12* d3d12;
if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void**)&d3d12) || !d3d12)
{
ERROR_LOG_FMT(VIDEO, "Failed to get HW rendering interface!");
return;
}
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 {} or {}, got {}!",
RETRO_HW_RENDER_INTERFACE_D3D12_VERSION,
RETRO_HW_RENDER_INTERFACE_D3D12_VERSION_2, d3d12->interface_version);
return;
}
if (!d3d12_library.IsOpen() && !d3d12_library.Open("d3d12.dll"))
{
d3d12_library.Close();
ERROR_LOG_FMT(VIDEO, "Failed to load D3D12 Libraries");
return;
}
if (!D3DCommon::LoadLibraries())
{
ERROR_LOG_FMT(VIDEO, "Failed to load the dxgi library");
return;
}
// Create D3D12 context with RetroArch's device and queue
ID3D12Device* ra_device = static_cast<ID3D12Device*>(d3d12->device);
ID3D12CommandQueue* ra_queue = static_cast<ID3D12CommandQueue*>(d3d12->queue);
if (!DX12::DXContext::CreateWithExternalDevice(ra_device, ra_queue))
{
ERROR_LOG_FMT(VIDEO, "Failed to create D3D12 context with external device");
return;
}
Microsoft::WRL::ComPtr<ID3D12Device1> device1;
if (FAILED(DX12::g_dx_context->GetDevice()->QueryInterface(IID_PPV_ARGS(&device1))))
{
WARN_LOG_FMT(VIDEO,
"Missing Direct3D 12.1+ support. Logical operations will not be supported.");
g_backend_info.bSupportsLogicOp = false;
}
if (!DX12::g_dx_context->CreateGlobalResources())
{
ERROR_LOG_FMT(VIDEO, "Failed to create D3D12 global resources");
DX12::DXContext::Destroy();
return;
}
UpdateActiveConfig();
std::unique_ptr<DX12Output> output = std::make_unique<DX12Output>(
d3d12, GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale);
if (!output->Create())
{
DX12::DXContext::Destroy();
return;
}
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>();
if(!g_video_backend->InitializeShared(std::move(gfx), std::move(vertex_manager),
std::move(perf_query), std::move(bounding_box)))
{
ERROR_LOG_FMT(VIDEO, "Failed to initialize shared components");
DX12::DXContext::Destroy();
return;
}
g_context_status.MarkInitialized();
return;
}
#endif
if(Video_InitializeBackend())
g_context_status.MarkInitialized();
}
bool Video_InitializeBackend()
{
WindowSystemInfo wsi = {};
wsi.type = WindowSystemType::Libretro;
int efbScale = Libretro::Options::GetCached<int>(
Libretro::Options::gfx_settings::EFB_SCALE, 1);
wsi.render_surface_scale = efbScale;
g_video_backend->PrepareWindow(wsi);
#ifdef HAS_VULKAN
if (hw_render.context_type == RETRO_HW_CONTEXT_VULKAN)
{
// 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());
UpdateActiveConfig();
// Remaining classes are also dependent on object cache.
Vulkan::g_object_cache = std::make_unique<Vulkan::ObjectCache>();
if (!Vulkan::g_object_cache->Initialize())
{
PanicAlertFmt("Failed to initialize Vulkan object cache.");
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);
if (!Vulkan::g_command_buffer_mgr->Initialize())
{
PanicAlertFmt("Failed to create Vulkan command buffers");
return false;
}
if (!Vulkan::StateTracker::CreateInstance())
{
PanicAlertFmt("Failed to create state tracker");
return false;
}
/* 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>();
return g_video_backend->InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
std::move(bounding_box));
}
#endif
if(!g_video_backend) {
WARN_LOG_FMT(VIDEO, "Video - g_video_backend - No supported renderer found");
return false;
}
// this calls InitializeGLExtensions, FillBackendInfo and InitializeShared internally
return g_video_backend->Initialize(wsi);
}
void ContextDestroy(void)
{
DEBUG_LOG_FMT(VIDEO, "Video - ContextDestroy!");
g_context_status.MarkDestroyed();
/* the backend's teardown uses the context */
D3D11Lock();
#ifdef HAS_OPENGL
if (g_gfx &&
Config::Get(Config::MAIN_GFX_BACKEND) == "OGL")
{
static_cast<OGL::OGLGfx*>(g_gfx.get())->SetSystemFrameBuffer(0);
}
#endif
if (g_video_backend)
{
g_video_backend->Shutdown();
}
switch (hw_render.context_type)
{
case RETRO_HW_CONTEXT_D3D12:
#ifdef _WIN32
DX12::DXContext::Destroy();
D3DCommon::UnloadLibraries();
d3d12_library.Close();
#endif
break;
case RETRO_HW_CONTEXT_D3D11:
#ifdef _WIN32
if (DX11::D3D::context)
{
DX11::D3D::context->ClearState();
DX11::D3D::context->Flush();
}
DX11::D3D::context.Reset();
DX11::D3D::device1.Reset();
DX11::D3D::device = nullptr;
DX11::D3D::device1 = nullptr;
DX11::D3D::context = nullptr;
DX11::D3D::stateman.reset();
D3DCommon::UnloadLibraries();
d3d11_library.Close();
#endif
break;
case RETRO_HW_CONTEXT_VULKAN:
#ifdef HAS_VULKAN
Vk::SetHWRenderInterface(nullptr);
#endif
break;
case RETRO_HW_CONTEXT_OPENGL:
case RETRO_HW_CONTEXT_OPENGL_CORE:
case RETRO_HW_CONTEXT_OPENGLES3:
break;
default:
case RETRO_HW_CONTEXT_NONE:
break;
}
D3D11Unlock();
#ifdef _WIN32
s_d3d11_v2 = nullptr;
s_d3d11_depth = 0;
#endif
}
#ifdef HAS_VULKAN
namespace Vk
{
const VkApplicationInfo* GetApplicationInfo(void)
{
static VkApplicationInfo app_info{VK_STRUCTURE_TYPE_APPLICATION_INFO};
app_info.pApplicationName = "Dolphin-Emu";
app_info.applicationVersion = 5; // TODO: extract from Common::scm_desc_str
app_info.pEngineName = "Dolphin-Emu";
app_info.engineVersion = 2;
app_info.apiVersion = VK_API_VERSION_1_0;
return &app_info;
}
#ifdef __APPLE__
VkInstance CreateInstance(PFN_vkGetInstanceProcAddr get_instance_proc_addr,
const VkApplicationInfo* app,
retro_vulkan_create_instance_wrapper_t create_instance_wrapper,
void* opaque)
{
VkInstanceCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.pApplicationInfo = app;
std::vector<const char*> extensions;
extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
create_info.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
create_info.ppEnabledExtensionNames = extensions.data();
static VkBool32 disable_arg_buffers = VK_FALSE;
VkLayerSettingEXT layer_setting = {};
layer_setting.pLayerName = "MoltenVK";
layer_setting.pSettingName = "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS";
layer_setting.type = VK_LAYER_SETTING_TYPE_BOOL32_EXT;
layer_setting.valueCount = 1;
layer_setting.pValues = &disable_arg_buffers;
VkLayerSettingsCreateInfoEXT layer_settings = {};
layer_settings.sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT;
layer_settings.settingCount = 1;
layer_settings.pSettings = &layer_setting;
create_info.pNext = &layer_settings;
return create_instance_wrapper(opaque, &create_info);
}
#endif
static bool CreateDeviceInternal(
retro_vulkan_context* context, VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface,
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(g_video_backend->GetConfigName() == "Vulkan");
Vulkan::LoadVulkanLibrary();
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);
if (!Vulkan::LoadVulkanInstanceFunctions(instance))
{
ERROR_LOG_FMT(VIDEO, "Failed to load Vulkan instance functions.");
Vulkan::UnloadVulkanLibrary();
return false;
}
Vulkan::VulkanContext::GPUList gpu_list = Vulkan::VulkanContext::EnumerateGPUs(instance);
if (gpu_list.empty())
{
ERROR_LOG_FMT(VIDEO, "No Vulkan physical devices available.");
Vulkan::UnloadVulkanLibrary();
return false;
}
Vulkan::VulkanContext::PopulateBackendInfo(&g_backend_info);
Vulkan::VulkanContext::PopulateBackendInfoAdapters(&g_backend_info, gpu_list);
if (gpu == VK_NULL_HANDLE)
gpu = gpu_list[0];
Vulkan::g_vulkan_context =
Vulkan::VulkanContext::Create(instance, gpu, surface, false, false, VK_API_VERSION_1_0);
if (!Vulkan::g_vulkan_context)
{
ERROR_LOG_FMT(VIDEO, "Failed to create Vulkan device");
Vulkan::UnloadVulkanLibrary();
return false;
}
context->gpu = Vulkan::g_vulkan_context->GetPhysicalDevice();
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;
}
bool CreateDevice(retro_vulkan_context* context, VkInstance instance, VkPhysicalDevice gpu,
VkSurfaceKHR surface, 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)
{
return CreateDeviceInternal(context, instance, gpu, surface, get_instance_proc_addr,
required_device_extensions, num_required_device_extensions,
required_device_layers, num_required_device_layers, required_features,
nullptr, nullptr);
}
bool CreateDevice2(retro_vulkan_context* context, VkInstance instance, VkPhysicalDevice gpu,
VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr,
retro_vulkan_create_device_wrapper_t create_device_wrapper, void* opaque)
{
if (!create_device_wrapper)
{
ERROR_LOG_FMT(VIDEO, "Vulkan negotiation v2 requires a device creation wrapper.");
return false;
}
return CreateDeviceInternal(context, instance, gpu, surface, get_instance_proc_addr, nullptr, 0,
nullptr, 0, nullptr, create_device_wrapper, opaque);
}
} // namespace Vk
#endif
/* The frontend's callback, behind one that counts what each retro_run
* hands over: a run whose fields only repeated the last picture draws
* nothing and reports a repeat, with the last picture's size. */
static retro_video_refresh_t s_frontend_video_cb;
static unsigned s_frames_sent;
static unsigned s_last_width;
static unsigned s_last_height;
static void CountedVideoRefresh(const void* data, unsigned width, unsigned height, size_t pitch)
{
s_frames_sent++;
if (data)
{
s_last_width = width;
s_last_height = height;
}
s_frontend_video_cb(data, width, height, pitch);
}
void EndFrame()
{
if (!s_frames_sent && s_last_width && s_last_height)
{
OutputRepeat();
s_frontend_video_cb(nullptr, s_last_width, s_last_height, 0);
}
s_frames_sent = 0;
}
} // namespace Video
} // namespace Libretro
void retro_set_video_refresh(retro_video_refresh_t cb)
{
Libretro::Video::s_frontend_video_cb = cb;
Libretro::Video::video_cb = Libretro::Video::CountedVideoRefresh;
}