D3D11: version 2 of the frontend interface

The core asks for version 2 of the D3D11 interface and, when it gets
it, shares the immediate context with the frontend by taking turns: it
holds the context lock around the frame's emulation, savestates, the
context reset and destroy and unloading, and releases it around
video_refresh and the sync index wait. It keeps one output texture per
sync index and names the frame's with set_texture, so a frontend
presenting from another thread reads it in place instead of copying
it.

When the frontend has used the context since the core last held it,
everything the core relies on is bound again: the state manager's
graphics and compute bindings and the viewport and scissor, which the
backend now keeps. Version 1 rebinds the same way after video_refresh.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-06 20:15:05 +01:00
1 parent 48356ca5f5
commit c09a3946df
9 files changed
+254 -88

No files matched your search

+3
View File
@@ -843,6 +843,8 @@ void retro_unload_game(void)
{
auto& system = Core::System::GetInstance();
/* the video backend's teardown uses the context */
Libretro::Video::D3D11Lock();
if (Core::IsRunning(system))
{
Core::Stop(system);
@@ -855,6 +857,7 @@ void retro_unload_game(void)
if (!g_context_status.IsDestroyed() && g_video_backend)
g_video_backend->Shutdown();
Libretro::Video::D3D11Unlock();
// these are disabled in Shutdown on fullscreen/window toggle
system.GetFifo().Shutdown();
+8
View File
@@ -543,6 +543,7 @@ void retro_run(void)
RETRO_PERFORMANCE_INIT(dolphin_main_func);
RETRO_PERFORMANCE_START(dolphin_main_func);
Libretro::Video::D3D11Lock();
if (system.IsDualCoreMode())
{
Core::DoFrameStep(system);
@@ -552,6 +553,7 @@ void retro_run(void)
{
system.GetCPU().RunSingleFrame();
}
Libretro::Video::D3D11Unlock();
RETRO_PERFORMANCE_STOP(dolphin_main_func);
@@ -577,10 +579,12 @@ size_t retro_serialize_size(void)
if (system.IsDualCoreMode())
ar->SetPassthrough(true);
Libretro::Video::D3D11Lock();
Core::RunOnCPUThread(Core::System::GetInstance(), [&] {
PointerWrap p((u8**)&size, sizeof(size_t), PointerWrap::Mode::Measure);
State::DoState(Core::System::GetInstance(), p);
}, true); // wait = true
Libretro::Video::D3D11Unlock();
if (system.IsDualCoreMode())
ar->SetPassthrough(false);
@@ -603,10 +607,12 @@ bool retro_serialize(void* data, size_t size)
if (!was_cpu)
Core::DeclareAsCPUThread();
Libretro::Video::D3D11Lock();
Core::RunOnCPUThread(Core::System::GetInstance(), [&] {
PointerWrap p((u8**)&data, size, PointerWrap::Mode::Write);
State::DoState(Core::System::GetInstance(), p);
}, true);
Libretro::Video::D3D11Unlock();
if (!was_cpu)
Core::UndeclareAsCPUThread();
@@ -632,10 +638,12 @@ bool retro_unserialize(const void* data, size_t size)
if (!was_cpu)
Core::DeclareAsCPUThread();
Libretro::Video::D3D11Lock();
Core::RunOnCPUThread(Core::System::GetInstance(), [&] {
PointerWrap p((u8**)&data, size, PointerWrap::Mode::Read);
State::DoState(Core::System::GetInstance(), p);
}, true);
Libretro::Video::D3D11Unlock();
if (!was_cpu)
Core::UndeclareAsCPUThread();
+60 -25
View File
@@ -6,6 +6,7 @@
#include "DolphinLibretro/Common/Globals.h"
#include "VideoCommon/TextureConfig.h"
#ifdef _WIN32
#include "VideoBackends/D3D/D3DGfx.h"
#include "VideoBackends/D3D12/D3D12Gfx.h"
#endif
#ifdef HAS_VULKAN
@@ -37,50 +38,84 @@ static TextureConfig OutputConfig(u32 width, u32 height)
#endif
#ifdef _WIN32
DX11Output::DX11Output(u32 width, u32 height) : m_width(width), m_height(height)
DX11Output::DX11Output(retro_hw_render_interface_d3d11* v2, u32 width, u32 height)
: m_v2(v2), m_width(width), m_height(height), m_current(0)
{
}
bool DX11Output::Create()
{
m_texture = DX11::DXTexture::Create(OutputConfig(m_width, m_height), "LibretroOutput");
if (!m_texture)
const unsigned mask = m_v2 ? m_v2->get_sync_index_mask(m_v2->handle) : 1u;
const u32 count = SlotCount(mask);
u32 i;
for (i = 0; i < count; i++)
{
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;
if (!(mask & (1u << i)))
continue;
m_textures[i] = DX11::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] = DX11::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* DX11Output::Acquire()
{
/* the frontend read the last picture inside video_refresh */
return m_framebuffer.get();
unsigned depth;
/* version 1: the frontend read the last picture inside video_refresh;
* version 2: it is done with this index's texture once the wait
* returns, which needs the context lock released */
if (!m_v2)
return m_framebuffers[0].get();
m_current = m_v2->get_sync_index(m_v2->handle);
depth = D3D11Suspend();
m_v2->wait_sync_index(m_v2->handle);
D3D11Resume(depth);
return m_framebuffers[m_current].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};
unsigned depth;
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);
if (m_v2)
{
m_v2->set_texture(m_v2->handle, m_textures[m_current]->GetD3DTexture());
depth = D3D11Suspend();
video_cb(RETRO_HW_FRAME_BUFFER_VALID, m_width, m_height, 0);
/* rebinds everything if the frontend drew meanwhile */
D3D11Resume(depth);
return;
}
video_cb(RETRO_HW_FRAME_BUFFER_VALID, m_width, m_height, 0);
{
ID3D11ShaderResourceView* srv = m_textures[0]->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::stateman->Restore();
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);
static_cast<DX11::Gfx*>(g_gfx.get())->RestoreContext();
}
}
DX12Output::DX12Output(retro_hw_render_interface_d3d12* iface, u32 width, u32 height)
+9 -5
View File
@@ -18,12 +18,14 @@ enum
};
#ifdef _WIN32
/* Direct3D 11: one target, which the frontend reads from pixel shader
* slot 0 while video_refresh runs. */
/* Direct3D 11: with version 2 of the interface one target per sync
* index, named with set_texture and read where it is; with version 1
* 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);
DX11Output(retro_hw_render_interface_d3d11* v2, u32 width, u32 height);
bool Create();
AbstractFramebuffer* Acquire() override;
@@ -33,10 +35,12 @@ public:
AbstractTextureFormat GetFormat() const override { return AbstractTextureFormat::RGBA8; }
private:
std::unique_ptr<DX11::DXTexture> m_texture;
std::unique_ptr<DX11::DXFramebuffer> m_framebuffer;
std::unique_ptr<DX11::DXTexture> m_textures[OUTPUT_MAX_SLOTS];
std::unique_ptr<DX11::DXFramebuffer> m_framebuffers[OUTPUT_MAX_SLOTS];
retro_hw_render_interface_d3d11* m_v2; /* NULL on version 1 */
u32 m_width;
u32 m_height;
u32 m_current;
};
/* Direct3D 12: with version 2 of the interface one target per sync
+136 -53
View File
@@ -70,6 +70,10 @@ 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()
@@ -158,6 +162,46 @@ void Init()
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!");
@@ -222,6 +266,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, 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;
}
@@ -285,6 +345,64 @@ bool SetHWRender(retro_hw_context_type type, const int version_major, const int
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!");
@@ -326,62 +444,19 @@ void ContextReset(void)
ERROR_LOG_FMT(VIDEO, "Failed to get HW rendering interface!");
return;
}
if (d3d->interface_version != RETRO_HW_RENDER_INTERFACE_D3D11_VERSION)
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 {}, got {}!",
RETRO_HW_RENDER_INTERFACE_D3D11_VERSION, d3d->interface_version);
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;
}
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>(
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();
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;
}
@@ -554,6 +629,9 @@ void ContextDestroy(void)
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")
@@ -608,6 +686,11 @@ void ContextDestroy(void)
case RETRO_HW_CONTEXT_NONE:
break;
}
D3D11Unlock();
#ifdef _WIN32
s_d3d11_v2 = nullptr;
s_d3d11_depth = 0;
#endif
}
#ifdef HAS_VULKAN
namespace Vk
+11
View File
@@ -43,6 +43,17 @@ bool SetHWRender(retro_hw_context_type type, const int version_major = -1, const
void ContextReset(void);
void ContextDestroy(void);
/* Direct3D 11 version 2 shares the immediate context with the frontend
* by taking turns: the core holds the lock around everything it does
* with the context, and never across video_refresh or a return to the
* frontend. Recursive; no-ops on other APIs and on version 1. */
void D3D11Lock(void);
void D3D11Unlock(void);
/* Releases every level held, for video_refresh or a wait on the
* frontend; returns them for D3D11Resume. */
unsigned D3D11Suspend(void);
void D3D11Resume(unsigned depth);
class SWGfx : public SW::SWGfx
{
public:
+13 -5
View File
@@ -124,17 +124,25 @@ void Gfx::SetPipeline(const AbstractPipeline* pipeline)
void Gfx::SetScissorRect(const MathUtil::Rectangle<int>& rc)
{
// TODO: Move to stateman
const CD3D11_RECT rect(rc.left, rc.top, std::max(rc.right, rc.left + 1),
std::max(rc.bottom, rc.top + 1));
D3D::context->RSSetScissorRects(1, &rect);
m_scissor = CD3D11_RECT(rc.left, rc.top, std::max(rc.right, rc.left + 1),
std::max(rc.bottom, rc.top + 1));
D3D::context->RSSetScissorRects(1, &m_scissor);
}
void Gfx::SetViewport(float x, float y, float width, float height, float near_depth,
float far_depth)
{
// TODO: Move to stateman
const CD3D11_VIEWPORT vp(x, y, width, height, near_depth, far_depth);
D3D::context->RSSetViewports(1, &vp);
m_viewport = CD3D11_VIEWPORT(x, y, width, height, near_depth, far_depth);
D3D::context->RSSetViewports(1, &m_viewport);
}
/* Everything bound on the context, again, after someone else used it. */
void Gfx::RestoreContext()
{
D3D::stateman->Restore();
D3D::context->RSSetViewports(1, &m_viewport);
D3D::context->RSSetScissorRects(1, &m_scissor);
}
void Gfx::Draw(u32 base_vertex, u32 num_vertices)
+5
View File
@@ -70,9 +70,14 @@ public:
SurfaceInfo GetSurfaceInfo() const override;
// Binds everything again after the frontend used the context.
void RestoreContext();
private:
StateCache m_state_cache;
D3D11_VIEWPORT m_viewport = {};
D3D11_RECT m_scissor = {};
float m_backbuffer_scale;
std::unique_ptr<FrameOutput> m_output;
};
@@ -338,6 +338,15 @@ void StateManager::Restore()
D3D::context->OMSetBlendState(m_pending.blendState, nullptr, 0xFFFFFFFF);
D3D::context->OMSetDepthStencilState(m_pending.depthState, 0);
D3D::context->RSSetState(m_pending.rasterizerState);
/* the compute stage's, as last set */
D3D::context->CSSetShader(m_compute_shader, nullptr, 0);
D3D::context->CSSetConstantBuffers(0, 1, &m_compute_constants);
D3D::context->CSSetShaderResources(0, static_cast<u32>(m_compute_textures.size()),
m_compute_textures.data());
D3D::context->CSSetSamplers(0, static_cast<u32>(m_compute_samplers.size()),
m_compute_samplers.data());
D3D::context->CSSetUnorderedAccessViews(0, static_cast<u32>(m_compute_images.size()),
m_compute_images.data(), nullptr);
m_current = m_pending;
m_dirtyFlags = 0;