mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 12:37:59 -10:00
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.
872 lines
28 KiB
C++
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;
|
|
}
|