From c09a3946dfb7d1103806e680876c3cb423711c87 Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Fri, 6 Nov 2026 20:02:21 +0100 Subject: [PATCH] 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. --- Source/Core/DolphinLibretro/Boot.cpp | 3 + Source/Core/DolphinLibretro/Main.cpp | 8 + Source/Core/DolphinLibretro/Output.cpp | 85 ++++++--- Source/Core/DolphinLibretro/Output.h | 14 +- Source/Core/DolphinLibretro/Video.cpp | 189 +++++++++++++++------ Source/Core/DolphinLibretro/Video.h | 11 ++ Source/Core/VideoBackends/D3D/D3DGfx.cpp | 18 +- Source/Core/VideoBackends/D3D/D3DGfx.h | 5 + Source/Core/VideoBackends/D3D/D3DState.cpp | 9 + 9 files changed, 254 insertions(+), 88 deletions(-) diff --git a/Source/Core/DolphinLibretro/Boot.cpp b/Source/Core/DolphinLibretro/Boot.cpp index 014339d1db..0fd1dacdc8 100644 --- a/Source/Core/DolphinLibretro/Boot.cpp +++ b/Source/Core/DolphinLibretro/Boot.cpp @@ -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(); diff --git a/Source/Core/DolphinLibretro/Main.cpp b/Source/Core/DolphinLibretro/Main.cpp index 521f39278f..14eec88e40 100644 --- a/Source/Core/DolphinLibretro/Main.cpp +++ b/Source/Core/DolphinLibretro/Main.cpp @@ -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(); diff --git a/Source/Core/DolphinLibretro/Output.cpp b/Source/Core/DolphinLibretro/Output.cpp index 7dd8dfdbdd..a2a40f77be 100644 --- a/Source/Core/DolphinLibretro/Output.cpp +++ b/Source/Core/DolphinLibretro/Output.cpp @@ -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(g_gfx.get())->RestoreContext(); + } } DX12Output::DX12Output(retro_hw_render_interface_d3d12* iface, u32 width, u32 height) diff --git a/Source/Core/DolphinLibretro/Output.h b/Source/Core/DolphinLibretro/Output.h index 21f45652e9..3541a8b10f 100644 --- a/Source/Core/DolphinLibretro/Output.h +++ b/Source/Core/DolphinLibretro/Output.h @@ -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 m_texture; - std::unique_ptr m_framebuffer; + std::unique_ptr m_textures[OUTPUT_MAX_SLOTS]; + std::unique_ptr 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 diff --git a/Source/Core/DolphinLibretro/Video.cpp b/Source/Core/DolphinLibretro/Video.cpp index a9e327e15c..87229f19ea 100644 --- a/Source/Core/DolphinLibretro/Video.cpp +++ b/Source/Core/DolphinLibretro/Video.cpp @@ -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(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::VideoBackend* d3d11 = static_cast(g_video_backend); + + if (!d3d11) + { + WARN_LOG_FMT(VIDEO, "D3D11 backend is null"); + return; + } + + d3d11->FillD3DBackendInfo(); + UpdateActiveConfig(); + + std::unique_ptr output = std::make_unique( + s_d3d11_v2, GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale); + if (!output->Create()) + return; + + auto gfx = std::make_unique(std::move(output), wsi.render_surface_scale); + auto vertex_manager = std::make_unique(); + auto perf_query = std::make_unique(); + auto bounding_box = std::make_unique(); + + 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::VideoBackend* d3d11 = static_cast(g_video_backend); - - if (!d3d11) - { - WARN_LOG_FMT(VIDEO, "D3D11 backend is null"); - return; - } - - d3d11->FillD3DBackendInfo(); - UpdateActiveConfig(); - - std::unique_ptr output = std::make_unique( - GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale); - if (!output->Create()) - return; - - auto gfx = std::make_unique(std::move(output), wsi.render_surface_scale); - auto vertex_manager = std::make_unique(); - auto perf_query = std::make_unique(); - auto bounding_box = std::make_unique(); - - 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 diff --git a/Source/Core/DolphinLibretro/Video.h b/Source/Core/DolphinLibretro/Video.h index fe8b3bde40..3fbe28d911 100644 --- a/Source/Core/DolphinLibretro/Video.h +++ b/Source/Core/DolphinLibretro/Video.h @@ -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: diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.cpp b/Source/Core/VideoBackends/D3D/D3DGfx.cpp index 7dbbfb01da..c7d45cb1d3 100644 --- a/Source/Core/VideoBackends/D3D/D3DGfx.cpp +++ b/Source/Core/VideoBackends/D3D/D3DGfx.cpp @@ -124,17 +124,25 @@ void Gfx::SetPipeline(const AbstractPipeline* pipeline) void Gfx::SetScissorRect(const MathUtil::Rectangle& 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) diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.h b/Source/Core/VideoBackends/D3D/D3DGfx.h index 66980a091c..3c75dec6aa 100644 --- a/Source/Core/VideoBackends/D3D/D3DGfx.h +++ b/Source/Core/VideoBackends/D3D/D3DGfx.h @@ -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 m_output; }; diff --git a/Source/Core/VideoBackends/D3D/D3DState.cpp b/Source/Core/VideoBackends/D3D/D3DState.cpp index 3b09833a15..0b06062437 100644 --- a/Source/Core/VideoBackends/D3D/D3DState.cpp +++ b/Source/Core/VideoBackends/D3D/D3DState.cpp @@ -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(m_compute_textures.size()), + m_compute_textures.data()); + D3D::context->CSSetSamplers(0, static_cast(m_compute_samplers.size()), + m_compute_samplers.data()); + D3D::context->CSSetUnorderedAccessViews(0, static_cast(m_compute_images.size()), + m_compute_images.data(), nullptr); m_current = m_pending; m_dirtyFlags = 0;