mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (DirectVulkan): build offscreen targets of a window-created renderer through AImageReader instead of a null headless entry point
This commit is contained in:
@@ -370,8 +370,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const auto suspended = m_suspendedWindowSurfaces.find(m_activatingSurface);
|
||||
if (suspended != m_suspendedWindowSurfaces.end()) {
|
||||
if (UsesSurfaceTargets() && pVulkanRenderer) {
|
||||
ActivateSuspendedPlaceholder(m_activatingSurface, suspended->second);
|
||||
return true;
|
||||
return ActivateSuspendedPlaceholder(m_activatingSurface, suspended->second);
|
||||
}
|
||||
return InitPbufferSurface(static_cast<EGLint>(suspended->second.Width),
|
||||
static_cast<EGLint>(suspended->second.Height));
|
||||
@@ -386,8 +385,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VulkanRendererConfig config;
|
||||
config.SwapInterval = GetRequestedSwapInterval();
|
||||
if (UsesSurfaceTargets() && pVulkanRenderer) {
|
||||
pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(m_activatingSurface), nativeWindow, config);
|
||||
return true;
|
||||
return pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(m_activatingSurface), nativeWindow, config);
|
||||
}
|
||||
// Any renderer instance this assignment replaces is destroyed here;
|
||||
// fence/timer-query handles stamped with the old generation go stale.
|
||||
@@ -404,8 +402,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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;
|
||||
return pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(m_activatingSurface), NativeWindowType{},
|
||||
config);
|
||||
}
|
||||
// Any renderer instance this assignment replaces is destroyed here;
|
||||
// fence/timer-query handles stamped with the old generation go stale.
|
||||
@@ -511,7 +509,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VulkanRendererConfig config;
|
||||
config.SurfaceWidth = static_cast<Uint32>(state->Width);
|
||||
config.SurfaceHeight = static_cast<Uint32>(state->Height);
|
||||
pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(surface), NativeWindowType{}, config);
|
||||
(void)pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(surface), NativeWindowType{}, config);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -610,7 +608,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
BackendObject::ReleaseEGLResources();
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::ActivateSuspendedPlaceholder(EGLSurface surface,
|
||||
Bool BackendObject_DirectVulkan::ActivateSuspendedPlaceholder(EGLSurface surface,
|
||||
const SuspendedWindowSurface& extent) {
|
||||
// A pbuffer target at the window's extent: every default-framebuffer path (render passes,
|
||||
// read-back, the shared-image blit, the acquire and the present) keeps a live swapchain to
|
||||
@@ -618,7 +616,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VulkanRendererConfig config;
|
||||
config.SurfaceWidth = std::max<Uint32>(extent.Width, 1);
|
||||
config.SurfaceHeight = std::max<Uint32>(extent.Height, 1);
|
||||
pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(surface), NativeWindowType{}, config);
|
||||
return pVulkanRenderer->ActivateSurfaceTarget(SurfaceTargetKey(surface), NativeWindowType{}, config);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::SuspendServerWindow(void* window) {
|
||||
@@ -648,7 +646,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// swapchain and VkSurfaceKHR - nothing references the window after this.
|
||||
pVulkanRenderer->DestroySurfaceTarget(key);
|
||||
m_suspendedWindowSurfaces[client] = extent;
|
||||
if (active) ActivateSuspendedPlaceholder(client, extent);
|
||||
// A placeholder that cannot be built is a suspend that failed: the window is already let
|
||||
// go of, and the caller ends the session by name instead (its EGL resources go).
|
||||
if (active && !ActivateSuspendedPlaceholder(client, extent)) return false;
|
||||
} else {
|
||||
m_suspendedWindowSurfaces[client] = extent;
|
||||
}
|
||||
@@ -686,7 +686,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
config.SurfaceWidth = std::max<Uint32>(width, 1);
|
||||
config.SurfaceHeight = std::max<Uint32>(height, 1);
|
||||
config.SwapInterval = GetRequestedSwapInterval();
|
||||
pVulkanRenderer->ActivateSurfaceTarget(key, reinterpret_cast<NativeWindowType>(window), config);
|
||||
if (!pVulkanRenderer->ActivateSurfaceTarget(key, reinterpret_cast<NativeWindowType>(window), config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MGLOG_I("DirectVulkan: %zu suspended window surface(s) rebuilt on server window %p (%ux%u)", suspended.size(),
|
||||
window, width, height);
|
||||
|
||||
@@ -81,8 +81,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 Height = 1;
|
||||
};
|
||||
UnorderedMap<EGLSurface, SuspendedWindowSurface> m_suspendedWindowSurfaces;
|
||||
// The placeholder pbuffer target of a suspended surface, made active.
|
||||
void ActivateSuspendedPlaceholder(EGLSurface surface, const SuspendedWindowSurface& extent);
|
||||
// The placeholder pbuffer target of a suspended surface, made active. False: it could not be built.
|
||||
Bool ActivateSuspendedPlaceholder(EGLSurface surface, const SuspendedWindowSurface& extent);
|
||||
};
|
||||
|
||||
// Single-source-of-truth helpers shared with the driver POST
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/OffscreenSurfaceRoute.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// How an offscreen (pbuffer) surface target gets its VkSurfaceKHR on Android, decided from what the
|
||||
// INSTANCE has, not from what the loader could offer. A renderer created for a window enabled
|
||||
// VK_KHR_android_surface and nothing headless, so its later offscreen targets (a second client
|
||||
// surface, or the placeholder a suspended server window draws into) must take the AImageReader
|
||||
// route; asking that instance for vkCreateHeadlessSurfaceEXT answers null. Pure, so a host case pins
|
||||
// it without a driver.
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
enum class OffscreenSurfaceRoute : Uint8 {
|
||||
Headless, // vkCreateHeadlessSurfaceEXT
|
||||
AndroidImageReader, // vkCreateAndroidSurfaceKHR on an AImageReader's window nobody displays
|
||||
Unavailable, // neither: the target cannot be built (the caller refuses, by name)
|
||||
};
|
||||
|
||||
// `headlessEnabled`: the instance was created with VK_EXT_headless_surface.
|
||||
// `headlessEntryPoint`: vkGetInstanceProcAddr answered vkCreateHeadlessSurfaceEXT.
|
||||
// `androidSurfaceEnabled`: the instance was created with VK_KHR_android_surface.
|
||||
constexpr OffscreenSurfaceRoute ChooseOffscreenSurfaceRoute(Bool headlessEnabled, Bool headlessEntryPoint,
|
||||
Bool androidSurfaceEnabled) {
|
||||
if (headlessEnabled && headlessEntryPoint) return OffscreenSurfaceRoute::Headless;
|
||||
if (androidSurfaceEnabled) return OffscreenSurfaceRoute::AndroidImageReader;
|
||||
return OffscreenSurfaceRoute::Unavailable;
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "VulkanRenderer.h"
|
||||
#include "SubmitFencePrefix.h"
|
||||
#include "OffscreenSurfaceRoute.h"
|
||||
|
||||
#include "MG_Backend/DirectVulkan/SubgroupSupportPolicy.h"
|
||||
#include "MG_Backend/DirectGLES/Utils.h"
|
||||
@@ -3267,7 +3268,7 @@ void main() {
|
||||
MagmaPipeValidateSubsystemConfiguration();
|
||||
#endif
|
||||
CreateInstance();
|
||||
CreateSurface();
|
||||
if (!CreateSurface()) throw RuntimeError("DirectVulkan: the initial surface could not be created");
|
||||
PickPhysicalDevice();
|
||||
CreateLogicalDeviceAndQueues();
|
||||
CreateAllocator();
|
||||
@@ -14807,6 +14808,7 @@ void main() {
|
||||
MGLOG_I("%s not available; falling back to an AImageReader %s surface for the pbuffer context.",
|
||||
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
||||
exts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
||||
m_androidSurfaceEnabled = true;
|
||||
}
|
||||
#elif defined VK_USE_PLATFORM_XLIB_KHR
|
||||
// An offscreen surface has ZERO window-system dependence, by design and on
|
||||
@@ -14833,6 +14835,7 @@ void main() {
|
||||
} else {
|
||||
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
||||
exts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
||||
m_androidSurfaceEnabled = true;
|
||||
#elif defined VK_USE_PLATFORM_WIN32_KHR
|
||||
exts.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
||||
#elif defined VK_USE_PLATFORM_METAL_EXT
|
||||
@@ -16438,42 +16441,66 @@ void main() {
|
||||
MGLOG_I("Command pool created");
|
||||
}
|
||||
|
||||
void VulkanRenderer::CreateSurface() {
|
||||
Bool VulkanRenderer::CreateSurface() {
|
||||
if (!m_window) {
|
||||
#if defined VK_USE_PLATFORM_METAL_EXT
|
||||
m_window = reinterpret_cast<NativeWindowType>(
|
||||
CreateInternalMetalLayer(m_config.SurfaceWidth, m_config.SurfaceHeight, &m_platformDisplay));
|
||||
m_platformLibrary = reinterpret_cast<void*>(m_window);
|
||||
#elif defined VK_USE_PLATFORM_ANDROID_KHR
|
||||
if (m_headlessSurfaceSupported) {
|
||||
auto* createHeadlessSurface = reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
|
||||
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
|
||||
MOBILEGL_ASSERT(createHeadlessSurface != nullptr,
|
||||
"VK_EXT_headless_surface is not available for DirectVulkan pbuffer surface");
|
||||
// EVERY ENTRY POINT HERE IS OPTIONAL, and each refusal is a `return false`, not
|
||||
// MOBILEGL_ASSERT: that macro is compiled out at the INFO level every shipping build uses,
|
||||
// and a renderer created for a WINDOW never enabled the headless extension - asking its
|
||||
// instance for vkCreateHeadlessSurfaceEXT answers null, and calling that crashed the server
|
||||
// the first time such a renderer needed an offscreen target.
|
||||
auto* createHeadlessSurface =
|
||||
m_headlessSurfaceSupported ? reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
|
||||
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"))
|
||||
: nullptr;
|
||||
const OffscreenSurfaceRoute route = ChooseOffscreenSurfaceRoute(
|
||||
m_headlessSurfaceSupported, createHeadlessSurface != nullptr, m_androidSurfaceEnabled);
|
||||
if (route == OffscreenSurfaceRoute::Headless) {
|
||||
VkHeadlessSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT};
|
||||
VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface),
|
||||
"vkCreateHeadlessSurfaceEXT failed");
|
||||
return;
|
||||
const VkResult result = createHeadlessSurface(m_instance, &sci, nullptr, &m_surface);
|
||||
if (result != VK_SUCCESS) {
|
||||
MGLOG_E("DirectVulkan: vkCreateHeadlessSurfaceEXT failed (%d) for an offscreen target",
|
||||
static_cast<int>(result));
|
||||
m_surface = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (route == OffscreenSurfaceRoute::Unavailable) {
|
||||
MGLOG_E("DirectVulkan: no offscreen surface route on this instance (neither %s nor %s enabled)",
|
||||
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
||||
return false;
|
||||
}
|
||||
// Windowless context on a driver without VK_EXT_headless_surface: give
|
||||
// the WSI an AImageReader's ANativeWindow. It is a real, valid producer
|
||||
// surface that is attached to no display and whose images this code never
|
||||
// acquires, which is exactly the "drawable nobody sees" the Xlib fallback
|
||||
// below builds out of an unmapped window. libmediandk is dlopen'd rather
|
||||
// than linked so a device without it degrades to the old error instead of
|
||||
// than linked so a device without it degrades to a refusal instead of
|
||||
// failing to load the library at all.
|
||||
{
|
||||
void* mediaLib = dlopen("libmediandk.so", RTLD_NOW | RTLD_LOCAL);
|
||||
MOBILEGL_ASSERT(mediaLib != nullptr,
|
||||
"VK_EXT_headless_surface is unavailable and libmediandk.so could not be loaded "
|
||||
"for the pbuffer surface fallback");
|
||||
if (mediaLib == nullptr) {
|
||||
MGLOG_E("DirectVulkan: libmediandk.so could not be loaded for an offscreen target");
|
||||
return false;
|
||||
}
|
||||
using AImageReaderNewFn = int (*)(int32_t, int32_t, int32_t, int32_t, void**);
|
||||
using AImageReaderGetWindowFn = int (*)(void*, void**);
|
||||
using AImageReaderDeleteFn = void (*)(void*);
|
||||
auto* imageReaderNew = reinterpret_cast<AImageReaderNewFn>(dlsym(mediaLib, "AImageReader_new"));
|
||||
auto* imageReaderGetWindow =
|
||||
reinterpret_cast<AImageReaderGetWindowFn>(dlsym(mediaLib, "AImageReader_getWindow"));
|
||||
MOBILEGL_ASSERT(imageReaderNew != nullptr && imageReaderGetWindow != nullptr,
|
||||
"libmediandk.so is missing AImageReader_new/AImageReader_getWindow");
|
||||
auto* imageReaderDelete =
|
||||
reinterpret_cast<AImageReaderDeleteFn>(dlsym(mediaLib, "AImageReader_delete"));
|
||||
if (imageReaderNew == nullptr || imageReaderGetWindow == nullptr || imageReaderDelete == nullptr) {
|
||||
MGLOG_E("DirectVulkan: libmediandk.so is missing AImageReader_new/getWindow/delete");
|
||||
dlclose(mediaLib);
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr int32_t kAndroidFormatRgba8888 = 0x1; // AIMAGE_FORMAT_RGBA_8888
|
||||
const int32_t width = static_cast<int32_t>(std::max<Uint32>(m_config.SurfaceWidth, 1));
|
||||
@@ -16482,12 +16509,16 @@ void main() {
|
||||
// maxImages must cover the swapchain's images; the reader never
|
||||
// acquires any, so this only sizes its buffer queue.
|
||||
const int status = imageReaderNew(width, height, kAndroidFormatRgba8888, 8, &reader);
|
||||
MOBILEGL_ASSERT(status == 0 && reader != nullptr,
|
||||
"AImageReader_new failed (%d) for the pbuffer surface fallback", status);
|
||||
void* nativeWindow = nullptr;
|
||||
const int windowStatus = imageReaderGetWindow(reader, &nativeWindow);
|
||||
MOBILEGL_ASSERT(windowStatus == 0 && nativeWindow != nullptr,
|
||||
"AImageReader_getWindow failed (%d) for the pbuffer surface fallback", windowStatus);
|
||||
const int windowStatus =
|
||||
(status == 0 && reader != nullptr) ? imageReaderGetWindow(reader, &nativeWindow) : -1;
|
||||
if (windowStatus != 0 || nativeWindow == nullptr) {
|
||||
MGLOG_E("DirectVulkan: no AImageReader window for an offscreen target (new %d, getWindow %d)",
|
||||
status, windowStatus);
|
||||
if (reader != nullptr) imageReaderDelete(reader);
|
||||
dlclose(mediaLib);
|
||||
return false;
|
||||
}
|
||||
m_fallbackImageReader = reader;
|
||||
m_platformLibrary = mediaLib;
|
||||
m_window = reinterpret_cast<NativeWindowType>(nativeWindow);
|
||||
@@ -16510,7 +16541,7 @@ void main() {
|
||||
VkHeadlessSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT};
|
||||
VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface),
|
||||
"vkCreateHeadlessSurfaceEXT failed");
|
||||
return;
|
||||
return true;
|
||||
#else
|
||||
auto* createHeadlessSurface =
|
||||
reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
|
||||
@@ -16526,7 +16557,7 @@ void main() {
|
||||
VkHeadlessSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT};
|
||||
VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface),
|
||||
"vkCreateHeadlessSurfaceEXT failed");
|
||||
return;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
#if defined VK_USE_PLATFORM_ANDROID_KHR
|
||||
@@ -16535,7 +16566,12 @@ void main() {
|
||||
|
||||
VkAndroidSurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR};
|
||||
sci.window = nativeWindow;
|
||||
VK_VERIFY(vkCreateAndroidSurfaceKHR(m_instance, &sci, nullptr, &m_surface), "vkCreateAndroidSurfaceKHR failed");
|
||||
const VkResult androidSurfaceResult = vkCreateAndroidSurfaceKHR(m_instance, &sci, nullptr, &m_surface);
|
||||
if (androidSurfaceResult != VK_SUCCESS) {
|
||||
MGLOG_E("DirectVulkan: vkCreateAndroidSurfaceKHR failed (%d)", static_cast<int>(androidSurfaceResult));
|
||||
m_surface = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
#elif defined VK_USE_PLATFORM_WIN32_KHR
|
||||
auto hwnd = static_cast<HWND>(m_window);
|
||||
MOBILEGL_ASSERT(hwnd, "HWND is null");
|
||||
@@ -16606,6 +16642,7 @@ void main() {
|
||||
MGLOG_W("VulkanRenderer::Initialize called on a platform which is not supported yet"); // TODO: support more
|
||||
// platforms
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector<VkQueueFamilyProperties> VulkanRenderer::GetQueueFamilyFromPhysicalDevice(VkPhysicalDevice device) {
|
||||
@@ -16947,9 +16984,9 @@ void main() {
|
||||
InvalidateSetupDrawSnapshots();
|
||||
}
|
||||
|
||||
void VulkanRenderer::ActivateSurfaceTarget(Uint64 key, NativeWindowType window,
|
||||
Bool VulkanRenderer::ActivateSurfaceTarget(Uint64 key, NativeWindowType window,
|
||||
const VulkanRendererConfig& surfaceConfig) {
|
||||
if (key == m_activeTargetKey && m_surface != VK_NULL_HANDLE) return;
|
||||
if (key == m_activeTargetKey && m_surface != VK_NULL_HANDLE) return true;
|
||||
ParkActiveTarget();
|
||||
auto parked = m_parkedTargets.find(key);
|
||||
if (parked != m_parkedTargets.end()) {
|
||||
@@ -16967,7 +17004,17 @@ void main() {
|
||||
m_swapchainSwapInterval.reset();
|
||||
m_swapchainResizeRequested = false;
|
||||
m_presentSuspended = false;
|
||||
CreateSurface();
|
||||
if (!CreateSurface()) {
|
||||
// Nothing of the half-built target stays active: its image reader and library go
|
||||
// with it, and the renderer has no target until the caller activates another.
|
||||
SurfaceTarget failed;
|
||||
ExchangeActiveTarget(failed);
|
||||
DestroyParkedTarget(failed);
|
||||
m_activeTargetKey = 0;
|
||||
MGLOG_E("DirectVulkan: surface target for key %llu could not be built (%s)",
|
||||
static_cast<unsigned long long>(key), window != NativeWindowType{} ? "window" : "pbuffer");
|
||||
return false;
|
||||
}
|
||||
CreateSwapchain();
|
||||
m_defaultFramebufferImageIndex = 0;
|
||||
VK_VERIFY(m_frameContext.InitializeSwapchainSemaphores(
|
||||
@@ -16988,6 +17035,7 @@ void main() {
|
||||
}
|
||||
m_activeTargetKey = key;
|
||||
OnActiveTargetChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::DestroyParkedTarget(SurfaceTarget& target) {
|
||||
|
||||
@@ -453,7 +453,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// 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);
|
||||
// False when a NEW target's surface could not be built (logged); the renderer is then left
|
||||
// with no active target - every present drops its frame - and the caller refuses by name.
|
||||
Bool 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);
|
||||
// The default framebuffer's extent (surface space, what PublishDefaultFramebufferInfo
|
||||
@@ -944,7 +946,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// a substituted window. (Android is the one exception and has its own path -
|
||||
// no Mali/Adreno driver seen so far exposes the extension, so a windowless
|
||||
// context is given an AImageReader ANativeWindow that is never displayed.)
|
||||
Bool m_headlessSurfaceSupported = true;
|
||||
// ENABLED ON THIS INSTANCE, not merely offered: a renderer created for a window never asks
|
||||
// for the extension, and its later offscreen targets must not call an entry point the
|
||||
// instance does not have (OffscreenSurfaceRoute.h). Hence false until CreateInstance says so.
|
||||
Bool m_headlessSurfaceSupported = false;
|
||||
// VK_KHR_android_surface was enabled on this instance (the AImageReader route needs it).
|
||||
Bool m_androidSurfaceEnabled = false;
|
||||
// Android has the same shortfall: no Mali/Adreno driver seen so far exposes
|
||||
// VK_EXT_headless_surface, so a windowless (EGL pbuffer) context gets an
|
||||
// AImageReader's ANativeWindow to hand the WSI instead. Nothing is ever
|
||||
@@ -1963,7 +1970,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkResult SetupDebugReportCallback();
|
||||
void DestroyDebugReportCallback();
|
||||
VkDebugUtilsMessengerCreateInfoEXT PopulateDebugMessengerCreateInfo();
|
||||
void CreateSurface();
|
||||
// False (logged) when the target's surface cannot be built; the renderer then has no surface.
|
||||
Bool CreateSurface();
|
||||
void PickPhysicalDevice();
|
||||
void CreateLogicalDeviceAndQueues();
|
||||
void CreateAllocator();
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <MG_Backend/DirectGLES/Utils.h>
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/OffscreenSurfaceRoute.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
#include <MG_Impl/Pipe/ResourceTracker.h>
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
@@ -3733,6 +3734,30 @@ TEST(ServerLoopSuspendTest, ABackendThatCannotSuspendStillLatchesServerWindowLos
|
||||
|
||||
// Mid-stream: answered within the stream (DrainRing's per-pop check) and the stream goes on. Red with
|
||||
// the suspend arm deleted: the stream is cut at the latch (bits 4|8|16).
|
||||
// MAGMA'S PLACEHOLDER ON A RENDERER CREATED FOR A WINDOW (the device crash: SIGSEGV at pc 0 in
|
||||
// VulkanRenderer::CreateSurface on screen-off). The on-screen session's renderer was built for the
|
||||
// server's window, so its instance enabled the Android surface extension and nothing headless; the
|
||||
// suspend's offscreen placeholder must take the AImageReader route - the way a windowless renderer's
|
||||
// pbuffers are built on drivers without the headless extension - and never call a headless entry point
|
||||
// the instance does not have. With no route at all the target is refused (and the suspend fails, so the
|
||||
// session latches as the cannot-suspend case below shows) instead of called through a null pointer.
|
||||
// Red with the route keyed on what the LOADER offers rather than what the instance enabled (the first
|
||||
// two expectations answer Headless).
|
||||
TEST(ServerLoopSuspendTest, AnOffscreenTargetNeverCallsAHeadlessEntryPointTheInstanceDoesNotHave) {
|
||||
using MG_Backend::DirectVulkan::ChooseOffscreenSurfaceRoute;
|
||||
using MG_Backend::DirectVulkan::OffscreenSurfaceRoute;
|
||||
// A renderer created for a window: Android surfaces enabled, the headless extension never enabled -
|
||||
// whether or not the loader would answer its entry point.
|
||||
EXPECT_EQ(ChooseOffscreenSurfaceRoute(false, false, true), OffscreenSurfaceRoute::AndroidImageReader);
|
||||
EXPECT_EQ(ChooseOffscreenSurfaceRoute(false, true, true), OffscreenSurfaceRoute::AndroidImageReader);
|
||||
// Enabled, but the entry point is absent: still never called.
|
||||
EXPECT_EQ(ChooseOffscreenSurfaceRoute(true, false, true), OffscreenSurfaceRoute::AndroidImageReader);
|
||||
EXPECT_EQ(ChooseOffscreenSurfaceRoute(true, false, false), OffscreenSurfaceRoute::Unavailable);
|
||||
EXPECT_EQ(ChooseOffscreenSurfaceRoute(false, false, false), OffscreenSurfaceRoute::Unavailable);
|
||||
// A windowless renderer on a driver that has the extension.
|
||||
EXPECT_EQ(ChooseOffscreenSurfaceRoute(true, true, false), OffscreenSurfaceRoute::Headless);
|
||||
}
|
||||
|
||||
TEST(ServerLoopSuspendTest, ALostServerWindowMidStreamSuspendsPromptlyAndTheStreamCarriesOn) {
|
||||
EXPECT_EXIT(SuspendMidStreamAndExit(), ::testing::ExitedWithCode(0), ".*")
|
||||
<< "bits: 1 = Detach did not answer ReleasedBySession; 2 = Detach took 1 s or more; 4 = the stream was "
|
||||
|
||||
Reference in New Issue
Block a user