mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (DirectVulkan): keep one renderer per session across its surfaces, follow bind_context to each context's surface, and keep the wire resource ops registered while any session lives
This commit is contained in:
@@ -596,6 +596,11 @@ namespace MobileGL {
|
||||
// native state is process-global by construction gives this object its own copy and binds
|
||||
// it to the calling thread. The default has nothing per process to separate.
|
||||
virtual void BindSessionStateToThisThread() {}
|
||||
// A served session's ring moved to another of its client's contexts (bind_context). A
|
||||
// backend whose surface follows the native context it binds needs nothing; one that keeps
|
||||
// a single native surface binding re-binds the surface that context was last made current
|
||||
// with. `token` is the client's context token (the EGLContext MakeEGLCurrent was given).
|
||||
virtual void OnClientContextBound(Uint64 token) { (void)token; }
|
||||
|
||||
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
||||
virtual Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
||||
|
||||
@@ -373,14 +373,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
auto nativeWindow = reinterpret_cast<NativeWindowType>(m_windowHandle.Handle);
|
||||
|
||||
VulkanRendererConfig config;
|
||||
config.SwapInterval = GetRequestedSwapInterval();
|
||||
if (UsesSurfaceTargets() && pVulkanRenderer) {
|
||||
pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(m_activatingSurface), nativeWindow, config);
|
||||
return true;
|
||||
}
|
||||
// Any renderer instance this assignment replaces is destroyed here;
|
||||
// fence/timer-query handles stamped with the old generation go stale.
|
||||
BumpRendererGeneration();
|
||||
VulkanRendererConfig config;
|
||||
config.SwapInterval = GetRequestedSwapInterval();
|
||||
pVulkanRenderer = MakeUnique<MG_Backend::DirectVulkan::VulkanRenderer>(nativeWindow, config);
|
||||
MOBILEGL_ASSERT(pVulkanRenderer != nullptr, "InitWindowSurface: VulkanRenderer creation failed");
|
||||
pVulkanRenderer->Initialize();
|
||||
pVulkanRenderer->SetActiveSurfaceTargetKey(SurfaceTargetKey(m_activatingSurface));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -388,12 +393,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VulkanRendererConfig config;
|
||||
config.SurfaceWidth = static_cast<Uint32>(std::max<EGLint>(width, 1));
|
||||
config.SurfaceHeight = static_cast<Uint32>(std::max<EGLint>(height, 1));
|
||||
if (UsesSurfaceTargets() && pVulkanRenderer) {
|
||||
pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(m_activatingSurface), NativeWindowType{}, config);
|
||||
return true;
|
||||
}
|
||||
// Any renderer instance this assignment replaces is destroyed here;
|
||||
// fence/timer-query handles stamped with the old generation go stale.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer = MakeUnique<MG_Backend::DirectVulkan::VulkanRenderer>(NativeWindowType{}, config);
|
||||
MOBILEGL_ASSERT(pVulkanRenderer != nullptr, "InitPbufferSurface: VulkanRenderer creation failed");
|
||||
pVulkanRenderer->Initialize();
|
||||
pVulkanRenderer->SetActiveSurfaceTargetKey(SurfaceTargetKey(m_activatingSurface));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -496,7 +506,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
Bool BackendObject_DirectVulkan::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
const Bool ok = BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
if (ok && UsesSurfaceTargets() && ctx != EGL_NO_CONTEXT && draw != EGL_NO_SURFACE) {
|
||||
m_contextBindings[ctx] = EGLCurrentState{.Display = dpy, .DrawSurface = draw, .ReadSurface = read,
|
||||
.Context = ctx};
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::OnClientContextBound(Uint64 token) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!UsesSurfaceTargets() || token == 0) return;
|
||||
// Each of a multi-window client's contexts draws to the surface IT was made current with
|
||||
// (Qt renders every window on its own thread and context); the ring interleaves them with
|
||||
// a bind_context between, and no make-current. Follow the context back to its surface.
|
||||
const auto found = m_contextBindings.find(reinterpret_cast<EGLContext>(token));
|
||||
if (found == m_contextBindings.end()) return;
|
||||
const EGLCurrentState binding = found->second;
|
||||
if (!GetRegisteredEGLSurface(binding.DrawSurface)) {
|
||||
m_contextBindings.erase(found);
|
||||
return;
|
||||
}
|
||||
const auto current = m_eglCurrentThreads.find(std::this_thread::get_id());
|
||||
if (current != m_eglCurrentThreads.end() && current->second.Context == binding.Context &&
|
||||
current->second.DrawSurface == binding.DrawSurface && m_eglSurface == binding.DrawSurface) {
|
||||
return;
|
||||
}
|
||||
(void)BackendObject::MakeEGLCurrent(binding.Display, binding.DrawSurface, binding.ReadSurface, binding.Context);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
@@ -531,8 +567,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
BackendObject::ReleaseEGLResources();
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::UsesSurfaceTargets() const {
|
||||
return MG_Config::Transport != MG_Config::TransportMode::Monolith;
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::OnEGLSurfaceForgotten(EGLSurface surface) {
|
||||
if (UsesSurfaceTargets() && pVulkanRenderer) pVulkanRenderer->DestroySurfaceTarget(SurfaceTargetKey(surface));
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||
(void)surface;
|
||||
// The renderer outlives its surfaces here: only this surface's target goes (in
|
||||
// OnEGLSurfaceForgotten, which follows), and every fence stays valid.
|
||||
if (UsesSurfaceTargets() && pVulkanRenderer) return;
|
||||
// Outstanding fence/timer-query handles now refer to a dead renderer;
|
||||
// treat them as signaled/available with zero results from here on.
|
||||
BumpRendererGeneration();
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
void Initialize() override;
|
||||
void BindSessionStateToThisThread() override;
|
||||
void OnClientContextBound(Uint64 token) override;
|
||||
Bool InitWindowSurface() override;
|
||||
Bool InitCapabilities() override;
|
||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||
@@ -50,6 +51,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
private:
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||
void OnEGLSurfaceForgotten(EGLSurface surface) override;
|
||||
// A served session keeps one renderer across all its client's surfaces and switches the
|
||||
// renderer's surface target instead (VulkanRenderer::ActivateSurfaceTarget).
|
||||
Bool UsesSurfaceTargets() const;
|
||||
static Uint64 SurfaceTargetKey(EGLSurface surface) { return reinterpret_cast<Uint64>(surface); }
|
||||
void UpdateAdvertisedExtensions();
|
||||
void UpdateDynamicBackendParameters();
|
||||
|
||||
@@ -60,6 +66,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// A served session's own copy of Magma's per-process state (MagmaSession.h), bound to its
|
||||
// apply thread. Null in the monolith and every other single-session shape.
|
||||
UniquePtr<MagmaSession> m_magmaSession;
|
||||
// The binding each client context was last made current with, for OnClientContextBound.
|
||||
UnorderedMap<EGLContext, EGLCurrentState> m_contextBindings;
|
||||
};
|
||||
|
||||
// Single-source-of-truth helpers shared with the driver POST
|
||||
|
||||
@@ -300,6 +300,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return packet;
|
||||
}
|
||||
|
||||
VkResult FrameContext::AcquireNextImageInCurrentSlot(VkDevice device, VkSwapchainKHR swapchain,
|
||||
Uint32& outImageIndex) {
|
||||
auto& frame = GetCurrent();
|
||||
const VkResult result = vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, frame.imageAvailableSemaphore,
|
||||
VK_NULL_HANDLE, &outImageIndex);
|
||||
if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) frame.imageAvailableSemaphoreConsumed = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
VkResult FrameContext::WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
||||
Uint64 timeout, VkFence acquireFence) {
|
||||
auto& frame = GetCurrent();
|
||||
|
||||
@@ -101,6 +101,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
SubmitInfoPacket GetSubmitInfo(Bool shouldSubmitCommandBuffer, Uint32 swapchainImageIndex) const;
|
||||
PresentInfoPacket GetPresentInfo(VkSwapchainKHR swapchain, Uint32 imageIndex) const;
|
||||
// Acquires into the current slot's semaphore WITHOUT the slot-fence wait: for a surface
|
||||
// brought up mid-frame, whose slot fence this frame has already waited and reset. The
|
||||
// slot's semaphore must be unsignaled with no pending signal (consumed).
|
||||
VkResult AcquireNextImageInCurrentSlot(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex);
|
||||
// The per-swapchain-image render-finished semaphores belong to one swapchain; a renderer
|
||||
// that parks a surface parks them with it.
|
||||
void ExchangeSwapchainSemaphores(Vector<VkSemaphore>& other) { std::swap(m_swapchainImageRenderFinishedSemaphores, other); }
|
||||
VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
||||
Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE);
|
||||
|
||||
|
||||
@@ -299,6 +299,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MGLOG_I("Swapchain created, extent = %dx%d, swapchain imageCount = %d", m_extent.width, m_extent.height,
|
||||
imageCount);
|
||||
|
||||
PublishDefaultFramebufferInfo();
|
||||
}
|
||||
|
||||
void SwapchainObject::PublishDefaultFramebufferInfo() const {
|
||||
const VkExtent2D defaultFramebufferExtent = m_surfaceExtent;
|
||||
// Properly initialize Default FBO here
|
||||
auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
|
||||
const Int extentWidth = static_cast<Int>(defaultFramebufferExtent.width);
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 presentQueueFamily, Uint32 minImageCountHint, VkExtent2D desiredExtent,
|
||||
Optional<Int> swapInterval);
|
||||
void Shutdown(VkDevice device);
|
||||
// Tells the frontend (or, under a transport, the client) the default framebuffer's size
|
||||
// and formats. Create does this; a renderer switching between surfaces repeats it.
|
||||
void PublishDefaultFramebufferInfo() const;
|
||||
|
||||
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
||||
VkPresentModeKHR GetPresentMode() const { return m_presentMode; }
|
||||
|
||||
@@ -234,6 +234,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Diagnostic memory for DescribeWireBufferHistory: the last handles created and destroyed.
|
||||
struct WireBufferHistory {
|
||||
static constexpr SizeT kDepth = 256;
|
||||
MG_Pipe::MGPipeHandle created[kDepth]{};
|
||||
MG_Pipe::MGPipeHandle destroyed[kDepth]{};
|
||||
SizeT createdCount = 0;
|
||||
SizeT destroyedCount = 0;
|
||||
};
|
||||
SessionLocal<WireBufferHistory> g_wireBufferHistory;
|
||||
} // namespace
|
||||
|
||||
VkBufferManager::WireBufferResource* VkBufferManager::FindWireBuffer(MG_Pipe::MGPipeHandle res) {
|
||||
const auto found = m_wireBuffers.find(WireBufferKey(res));
|
||||
return found == m_wireBuffers.end() ? nullptr : &found->second;
|
||||
@@ -246,6 +258,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
std::abort();
|
||||
}
|
||||
m_wireBuffers.try_emplace(WireBufferKey(res));
|
||||
{
|
||||
auto& history = *g_wireBufferHistory;
|
||||
history.created[history.createdCount++ % 256] = res;
|
||||
}
|
||||
RespecifyWireBuffer(res, desc, nullptr);
|
||||
}
|
||||
|
||||
@@ -972,7 +988,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void VkBufferManager::DescribeWireBufferHistory(MG_Pipe::MGPipeHandle res) const {
|
||||
const auto& history = *g_wireBufferHistory;
|
||||
Int createdAgo = -1, destroyedAgo = -1;
|
||||
for (SizeT i = 0; i < std::min(history.createdCount, WireBufferHistory::kDepth); ++i) {
|
||||
const auto& h = history.created[(history.createdCount - 1 - i) % WireBufferHistory::kDepth];
|
||||
if (h.Slot == res.Slot && h.Gen == res.Gen) { createdAgo = static_cast<Int>(i); break; }
|
||||
}
|
||||
for (SizeT i = 0; i < std::min(history.destroyedCount, WireBufferHistory::kDepth); ++i) {
|
||||
const auto& h = history.destroyed[(history.destroyedCount - 1 - i) % WireBufferHistory::kDepth];
|
||||
if (h.Slot == res.Slot && h.Gen == res.Gen) { destroyedAgo = static_cast<Int>(i); break; }
|
||||
}
|
||||
MGLOG_E("Magma wire buffer {slot=%u, gen=%u}: live stores %zu; created %d creates ago, destroyed %d "
|
||||
"destroys ago (-1 = not in the last %zu); %zu creates, %zu destroys in this session",
|
||||
res.Slot, res.Gen, m_wireBuffers.size(), createdAgo, destroyedAgo, WireBufferHistory::kDepth,
|
||||
history.createdCount, history.destroyedCount);
|
||||
}
|
||||
|
||||
void VkBufferManager::DestroyWireBuffer(MG_Pipe::MGPipeHandle res) {
|
||||
{
|
||||
auto& history = *g_wireBufferHistory;
|
||||
history.destroyed[history.destroyedCount++ % WireBufferHistory::kDepth] = res;
|
||||
}
|
||||
const auto found = m_wireBuffers.find(WireBufferKey(res));
|
||||
if (found == m_wireBuffers.end()) return;
|
||||
DeferWireRelease(std::move(found->second.buffer),
|
||||
@@ -1135,7 +1173,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (g_activeBufferManager.Get() == this) {
|
||||
g_activeBufferManager.Get() = nullptr;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Pipe::MGPipeGetResourceOps() == &g_vulkanWireResourceOps) {
|
||||
// Under a transport the table is the PROCESS's and serves every session: it dispatches
|
||||
// to the calling session's own manager (WireManager), so one session's renderer going
|
||||
// away must not take it from the others - their buffer creates would be dropped from
|
||||
// then on, and every buffer born after that would be a handle the server never saw.
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith &&
|
||||
MG_Pipe::MGPipeGetResourceOps() == &g_vulkanWireResourceOps) {
|
||||
MG_Pipe::MGPipeSetResourceOps(nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -110,6 +110,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// frontend BufferObject. Acquires expose the full GPU store, without a CPU
|
||||
// pointer: CPU consumers must use ReadWireBuffer for ordered, current bytes.
|
||||
Bool AcquireWireSlice(BufferKind kind, MG_Pipe::MGPipeHandle res, BufferSlice& outSlice);
|
||||
// The handle names a store this session created (whatever its size, zero included).
|
||||
Bool IsKnownWireBuffer(MG_Pipe::MGPipeHandle res) { return FindWireBuffer(res) != nullptr; }
|
||||
// Names, for a Fatal about an unknown handle, whether this manager ever saw it.
|
||||
void DescribeWireBufferHistory(MG_Pipe::MGPipeHandle res) const;
|
||||
Bool ReadWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, void* dst);
|
||||
Bool CopyWireBufferRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
|
||||
const BufferSlice& dst);
|
||||
|
||||
@@ -607,6 +607,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
|
||||
if (isDefaultFbo) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &m_defaultFramebufferTarget, sizeof(m_defaultFramebufferTarget)));
|
||||
}
|
||||
// sRGB attachments switch between their sRGB and UNORM-twin views with this
|
||||
// capability (ResolveSrgbAttachmentWriteFormat), changing the render pass formats.
|
||||
@@ -1528,6 +1529,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return &insertedIt->second;
|
||||
}
|
||||
|
||||
void VkRenderPassManager::PurgeRenderPasses() {
|
||||
Vector<VkRenderPass> destroyedRenderPasses;
|
||||
destroyedRenderPasses.reserve(m_renderPasses.size());
|
||||
for (auto& [_, entry] : m_renderPasses) destroyedRenderPasses.push_back(entry.renderPass);
|
||||
m_renderPasses.clear();
|
||||
m_rpFastValid = false;
|
||||
if (!destroyedRenderPasses.empty() && m_evictionObserver != nullptr) {
|
||||
m_evictionObserver->OnRenderPassesDestroyed(destroyedRenderPasses);
|
||||
}
|
||||
}
|
||||
|
||||
void VkRenderPassManager::OnPresent() {
|
||||
++m_frameCounter;
|
||||
|
||||
|
||||
@@ -227,6 +227,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
Bool Initialize();
|
||||
void Shutdown();
|
||||
// The surface target the default framebuffer currently names (VulkanRenderer's surface
|
||||
// targets): a default-framebuffer pass of one surface is not another's.
|
||||
void SetDefaultFramebufferTarget(Uint64 serial) {
|
||||
m_defaultFramebufferTarget = serial;
|
||||
m_rpFastValid = false;
|
||||
}
|
||||
// Destroys every cached pass and framebuffer (not the renderbuffers). The device must be
|
||||
// idle and no pass may be open: a surface target's image views are going away.
|
||||
void PurgeRenderPasses();
|
||||
|
||||
HashType ComputeHash(
|
||||
const MG_State::GLState::FramebufferObject& fbo,
|
||||
@@ -303,6 +312,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch /
|
||||
// version change, swapchain rotation, any attachment image recreation (the two epochs),
|
||||
// or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed).
|
||||
Uint64 m_defaultFramebufferTarget = 0;
|
||||
Bool m_rpFastValid = false;
|
||||
const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr;
|
||||
// The FBO's never-reused lifetime id joins the raw pointer + Uint16 version:
|
||||
|
||||
@@ -3478,6 +3478,9 @@ void main() {
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
VK_VERIFY(vkDeviceWaitIdle(m_device));
|
||||
}
|
||||
// Parked surface targets (ActivateSurfaceTarget) go before the device and instance.
|
||||
for (auto& [_, target] : m_parkedTargets) DestroyParkedTarget(target);
|
||||
m_parkedTargets.clear();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P7 wave 2 package B3: the session's decline tally. Silent when nothing declined, so a
|
||||
// green lane stays quiet and a lane that dropped a draw cannot (rule I's observable).
|
||||
@@ -16658,6 +16661,8 @@ void main() {
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::RecreateSwapchain() {
|
||||
// No surface target is active (its surface was released): nothing to build on.
|
||||
if (m_surface == VK_NULL_HANDLE) return false;
|
||||
// Handle cases like minimize on Windows, where swapchain could return a 0x0 extent
|
||||
const auto swapchainCapabilities =
|
||||
SwapchainObject::GetSwapchainCapabilities(m_physicalDevice.handle, m_surface);
|
||||
@@ -16781,6 +16786,159 @@ void main() {
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::ExchangeActiveTarget(SurfaceTarget& target) {
|
||||
std::swap(m_activeTargetSerial, target.serial);
|
||||
std::swap(m_window, target.window);
|
||||
std::swap(m_platformDisplay, target.platformDisplay);
|
||||
std::swap(m_platformLibrary, target.platformLibrary);
|
||||
std::swap(m_platformCloseDisplay, target.platformCloseDisplay);
|
||||
std::swap(m_fallbackImageReader, target.fallbackImageReader);
|
||||
std::swap(m_config.SurfaceWidth, target.surfaceWidth);
|
||||
std::swap(m_config.SurfaceHeight, target.surfaceHeight);
|
||||
std::swap(m_config.SwapInterval, target.swapInterval);
|
||||
std::swap(m_presentsToAppWindow, target.presentsToAppWindow);
|
||||
std::swap(m_swapchainSwapInterval, target.swapchainSwapInterval);
|
||||
std::swap(m_swapchainResizeRequested, target.swapchainResizeRequested);
|
||||
std::swap(m_presentSuspended, target.presentSuspended);
|
||||
std::swap(m_surface, target.surface);
|
||||
std::swap(m_swapchainObject, target.swapchain);
|
||||
m_frameContext.ExchangeSwapchainSemaphores(target.renderFinishedSemaphores);
|
||||
std::swap(m_imageIndexAcquired, target.imageIndexAcquired);
|
||||
std::swap(m_defaultFramebufferImageIndex, target.defaultFramebufferImageIndex);
|
||||
}
|
||||
|
||||
void VulkanRenderer::ParkActiveTarget() {
|
||||
if (m_surface == VK_NULL_HANDLE) return;
|
||||
// The active surface's recorded work goes to the queue now, while its images are still
|
||||
// the default framebuffer's.
|
||||
FlushPendingCommands();
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
// Its acquired image keeps its acquire for when the surface comes back, but the signal
|
||||
// lives on THIS slot's semaphore, which the next surface acquires with. Wait it out with
|
||||
// an empty submission; the image is then simply held, and its next submit waits nothing.
|
||||
if (m_swapchainObject.GetHandle() != VK_NULL_HANDLE && !m_presentSuspended &&
|
||||
!frame.imageAvailableSemaphoreConsumed) {
|
||||
const VkFence fence = AcquirePooledSubmitFence();
|
||||
if (fence == VK_NULL_HANDLE || !SubmitPendingCommandBuffer(frame, fence, /*pooledFence=*/true)) {
|
||||
MGLOG_E("DirectVulkan: a parked surface's pending acquire could not be waited out");
|
||||
}
|
||||
}
|
||||
SurfaceTarget parked;
|
||||
ExchangeActiveTarget(parked);
|
||||
m_parkedTargets[m_activeTargetKey] = std::move(parked);
|
||||
m_activeTargetKey = 0;
|
||||
}
|
||||
|
||||
void VulkanRenderer::OnActiveTargetChanged() {
|
||||
if (m_swapchainObject.GetHandle() != VK_NULL_HANDLE) {
|
||||
m_swapchainObject.PublishDefaultFramebufferInfo();
|
||||
if (m_programFactory) m_programFactory->SetDefaultFramebufferHeight(m_swapchainObject.GetExtent().height);
|
||||
}
|
||||
if (m_renderPassManager) m_renderPassManager->SetDefaultFramebufferTarget(m_activeTargetSerial);
|
||||
InvalidatePipelineMemo();
|
||||
ResetDynamicStateShadow();
|
||||
InvalidateSetupDrawSnapshots();
|
||||
}
|
||||
|
||||
void VulkanRenderer::ActivateSurfaceTarget(Uint64 key, NativeWindowType window,
|
||||
const VulkanRendererConfig& surfaceConfig) {
|
||||
if (key == m_activeTargetKey && m_surface != VK_NULL_HANDLE) return;
|
||||
ParkActiveTarget();
|
||||
auto parked = m_parkedTargets.find(key);
|
||||
if (parked != m_parkedTargets.end()) {
|
||||
ExchangeActiveTarget(parked->second);
|
||||
m_parkedTargets.erase(parked);
|
||||
// Its acquire was waited when it was parked (ParkActiveTarget).
|
||||
m_frameContext.GetCurrent().imageAvailableSemaphoreConsumed = true;
|
||||
} else {
|
||||
m_activeTargetSerial = m_nextTargetSerial++;
|
||||
m_window = window;
|
||||
m_presentsToAppWindow = window != NativeWindowType{};
|
||||
m_config.SurfaceWidth = surfaceConfig.SurfaceWidth;
|
||||
m_config.SurfaceHeight = surfaceConfig.SurfaceHeight;
|
||||
m_config.SwapInterval = m_presentsToAppWindow ? surfaceConfig.SwapInterval : Nullopt;
|
||||
m_swapchainSwapInterval.reset();
|
||||
m_swapchainResizeRequested = false;
|
||||
m_presentSuspended = false;
|
||||
CreateSurface();
|
||||
CreateSwapchain();
|
||||
m_defaultFramebufferImageIndex = 0;
|
||||
VK_VERIFY(m_frameContext.InitializeSwapchainSemaphores(
|
||||
m_device, static_cast<Uint32>(m_swapchainObject.GetImageCount())),
|
||||
"ActivateSurfaceTarget, InitializeSwapchainSemaphores");
|
||||
if (m_swapchainObject.GetHandle() != VK_NULL_HANDLE) {
|
||||
VkResult result = m_frameContext.AcquireNextImageInCurrentSlot(
|
||||
m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
|
||||
if (result == VK_SUBOPTIMAL_KHR) result = VK_SUCCESS;
|
||||
VK_VERIFY(result, "ActivateSurfaceTarget, AcquireNextImageInCurrentSlot");
|
||||
m_defaultFramebufferImageIndex = m_imageIndexAcquired;
|
||||
} else {
|
||||
m_presentSuspended = true;
|
||||
}
|
||||
MGLOG_I("DirectVulkan: surface target %llu built (%ux%u, %s)",
|
||||
static_cast<unsigned long long>(m_activeTargetSerial), m_swapchainObject.GetExtent().width,
|
||||
m_swapchainObject.GetExtent().height, m_presentsToAppWindow ? "window" : "pbuffer");
|
||||
}
|
||||
m_activeTargetKey = key;
|
||||
OnActiveTargetChanged();
|
||||
}
|
||||
|
||||
void VulkanRenderer::DestroyParkedTarget(SurfaceTarget& target) {
|
||||
target.swapchain.Shutdown(m_device);
|
||||
for (VkSemaphore semaphore : target.renderFinishedSemaphores) {
|
||||
if (semaphore != VK_NULL_HANDLE) vkDestroySemaphore(m_device, semaphore, nullptr);
|
||||
}
|
||||
target.renderFinishedSemaphores.clear();
|
||||
if (target.surface != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(m_instance, target.surface, nullptr);
|
||||
target.surface = VK_NULL_HANDLE;
|
||||
}
|
||||
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
if (target.fallbackImageReader != nullptr && target.platformLibrary != nullptr) {
|
||||
using AImageReaderDeleteFn = void (*)(void*);
|
||||
auto* imageReaderDelete =
|
||||
reinterpret_cast<AImageReaderDeleteFn>(dlsym(target.platformLibrary, "AImageReader_delete"));
|
||||
if (imageReaderDelete) imageReaderDelete(target.fallbackImageReader);
|
||||
target.fallbackImageReader = nullptr;
|
||||
dlclose(target.platformLibrary);
|
||||
target.platformLibrary = nullptr;
|
||||
}
|
||||
#elif defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
if (target.platformDisplay != nullptr) {
|
||||
using XCloseDisplayFn = int (*)(Display*);
|
||||
auto* closeDisplay = reinterpret_cast<XCloseDisplayFn>(target.platformCloseDisplay);
|
||||
if (closeDisplay) closeDisplay(static_cast<Display*>(target.platformDisplay));
|
||||
target.platformDisplay = nullptr;
|
||||
}
|
||||
if (target.platformLibrary != nullptr) {
|
||||
dlclose(target.platformLibrary);
|
||||
target.platformLibrary = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanRenderer::DestroySurfaceTarget(Uint64 key) {
|
||||
if (key == m_activeTargetKey && m_surface != VK_NULL_HANDLE) ParkActiveTarget();
|
||||
auto parked = m_parkedTargets.find(key);
|
||||
if (parked == m_parkedTargets.end()) return;
|
||||
// Whatever is recorded may name the target's images (a blit out of it, say): submit it,
|
||||
// then let the GPU finish with everything before the views go.
|
||||
FlushPendingCommands();
|
||||
VK_VERIFY(vkDeviceWaitIdle(m_device));
|
||||
OnSubmitsCompletedUpTo(m_submitCounter);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
DestroyWireDrawPass();
|
||||
CollectWireObjects(m_submitCounter, true);
|
||||
ClearAllWireDrawPassCaches();
|
||||
#endif
|
||||
if (m_renderPassManager) m_renderPassManager->PurgeRenderPasses();
|
||||
InvalidatePipelineMemo();
|
||||
ResetDynamicStateShadow();
|
||||
InvalidateSetupDrawSnapshots();
|
||||
DestroyParkedTarget(parked->second);
|
||||
m_parkedTargets.erase(parked);
|
||||
}
|
||||
|
||||
VkInstance VulkanRenderer::GetInstance() const {
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
@@ -427,7 +427,52 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// no new swapchain is installed and presentation must stay suspended.
|
||||
Bool RecreateSwapchain();
|
||||
|
||||
// SURFACE TARGETS. One renderer - one device and every GL object's resources - serves
|
||||
// every EGL surface of its context's client; only the presentation side (VkSurfaceKHR,
|
||||
// swapchain, its render-finished semaphores, the acquired image, the pbuffer's image
|
||||
// reader) belongs to a surface. `key` names the client surface. The renderer starts with
|
||||
// the surface it was constructed for as its active target (SetActiveSurfaceTargetKey).
|
||||
// Activating another key parks the active target - its recorded work submitted, its
|
||||
// pending acquire consumed - and brings the other back, or builds it on first use.
|
||||
void SetActiveSurfaceTargetKey(Uint64 key) { m_activeTargetKey = key; }
|
||||
void ActivateSurfaceTarget(Uint64 key, NativeWindowType window, const VulkanRendererConfig& surfaceConfig);
|
||||
// Drops a surface's target, active or parked. A no-op for a key that has none.
|
||||
void DestroySurfaceTarget(Uint64 key);
|
||||
|
||||
private:
|
||||
// The presentation half of the renderer, parked while another surface is active. Its
|
||||
// fields mirror the renderer members ExchangeActiveTarget swaps them with.
|
||||
struct SurfaceTarget {
|
||||
Uint64 serial = 0;
|
||||
NativeWindowType window = 0;
|
||||
void* platformDisplay = nullptr;
|
||||
void* platformLibrary = nullptr;
|
||||
void* platformCloseDisplay = nullptr;
|
||||
void* fallbackImageReader = nullptr;
|
||||
Uint32 surfaceWidth = 0;
|
||||
Uint32 surfaceHeight = 0;
|
||||
Optional<Int> swapInterval;
|
||||
Bool presentsToAppWindow = false;
|
||||
Optional<Int> swapchainSwapInterval;
|
||||
Bool swapchainResizeRequested = false;
|
||||
Bool presentSuspended = false;
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
SwapchainObject swapchain;
|
||||
Vector<VkSemaphore> renderFinishedSemaphores;
|
||||
Uint imageIndexAcquired = 0;
|
||||
Uint defaultFramebufferImageIndex = 0;
|
||||
};
|
||||
void ExchangeActiveTarget(SurfaceTarget& target);
|
||||
// Submits the active target's work, consumes its pending acquire and moves it into
|
||||
// m_parkedTargets. Leaves the renderer with no target.
|
||||
void ParkActiveTarget();
|
||||
void OnActiveTargetChanged();
|
||||
void DestroyParkedTarget(SurfaceTarget& target);
|
||||
UnorderedMap<Uint64, SurfaceTarget> m_parkedTargets;
|
||||
Uint64 m_activeTargetKey = 0;
|
||||
Uint64 m_activeTargetSerial = 1;
|
||||
Uint64 m_nextTargetSerial = 2;
|
||||
|
||||
// Tiered emission for an already-set-up multi-draw batch (state bound, index
|
||||
// buffer bound for the indexed form). Tier 1: VK_EXT_multi_draw. Tier 2: one
|
||||
// vkCmdDraw(Indexed)Indirect over a transient command array. Tier 3: unrolled
|
||||
|
||||
@@ -306,8 +306,20 @@
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"Magma:client-vertex-array\"} location=%u: an "
|
||||
"attribute the program reads carries no buffer handle", location);
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager.AcquireWireSlice(BufferKind::Vertex, resource.Res, slice))
|
||||
MagmaWireFatal("vertex-buffer-resource");
|
||||
if (!m_bufferManager.AcquireWireSlice(BufferKind::Vertex, resource.Res, slice)) {
|
||||
// A buffer with no data store is legal GL to bind (glGenBuffers + glBindBuffer, no
|
||||
// glBufferData), and the client mints its handle at construction while the create
|
||||
// only crosses with the first respecify (PipeFill.cpp's M-1) - so the handle may be
|
||||
// one this server never saw, or one it holds with zero bytes. Either way the draw
|
||||
// reads no storage: it is declined, as the GL draws nothing defined from it.
|
||||
static Bool s_described = false;
|
||||
if (!s_described && !m_bufferManager.IsKnownWireBuffer(resource.Res)) {
|
||||
s_described = true;
|
||||
m_bufferManager.DescribeWireBufferHistory(resource.Res);
|
||||
}
|
||||
MGL_WIRE_DECLINE_AT(VertexAttributeWindow, "an enabled attribute's buffer has no storage");
|
||||
return false;
|
||||
}
|
||||
const VkDeviceSize offset = input.bindingBaseOffsets[binding];
|
||||
const auto type = static_cast<DataType>(attr.Type);
|
||||
const SizeT elementSize = VertexInputStateFactory::GetAttributeByteSize(type, attr.Size, attr.IsBgra);
|
||||
@@ -387,8 +399,16 @@
|
||||
const void* bytes = nullptr;
|
||||
if (cpuIndices) bytes = reinterpret_cast<const void*>(indices->indexByteOffset);
|
||||
else {
|
||||
if (!m_bufferManager.AcquireWireSlice(BufferKind::Index, state.IndexBuffer.Res, indexSlice))
|
||||
MagmaWireFatal("index-buffer-resource");
|
||||
if (!m_bufferManager.AcquireWireSlice(BufferKind::Index, state.IndexBuffer.Res, indexSlice)) {
|
||||
// As for a vertex buffer: a bound element buffer with no data store yet.
|
||||
static Bool s_described = false;
|
||||
if (!s_described && !m_bufferManager.IsKnownWireBuffer(state.IndexBuffer.Res)) {
|
||||
s_described = true;
|
||||
m_bufferManager.DescribeWireBufferHistory(state.IndexBuffer.Res);
|
||||
}
|
||||
MGL_WIRE_DECLINE_AT(IndexWindow, "the element buffer has no storage");
|
||||
return false;
|
||||
}
|
||||
const Uint64 offset = state.IndexBuffer.Offset + indices->indexByteOffset;
|
||||
if (offset < state.IndexBuffer.Offset || offset > indexSlice.size ||
|
||||
indices->indexByteSize > indexSlice.size - offset) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include "ServerSession.h"
|
||||
#include "ServerLoop.h"
|
||||
#include "../Transport/ReplySlot.h"
|
||||
#include "StagedTextureStore.h"
|
||||
|
||||
@@ -1504,6 +1505,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// without a driver call when the context it resolves to is already the one bound here.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
(void)MG_Backend::DirectGLES::MakeNativeContextCurrentForBoundToken();
|
||||
// The backend half for one that keeps a single native surface binding (Magma).
|
||||
if (MG_Backend::BackendObject* backend = ServerLoopInstance().Backend()) {
|
||||
backend->OnClientContextBound(bind.ClientContextToken);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user