From bd92c87d39517143b2503430830da86f694b2eae Mon Sep 17 00:00:00 2001 From: Adrian Cojocaru Date: Wed, 12 Aug 2026 12:21:24 +0300 Subject: [PATCH] fix(android): Workaround for Adreno `VK_ERROR_DEVICE_LOST` (#4442) --- include/mbgl/shaders/layer_ubo.hpp | 7 +- include/mbgl/shaders/vulkan/common.hpp | 8 +- .../shaders/vulkan/location_indicator.hpp | 6 +- include/mbgl/vulkan/context.hpp | 2 +- include/mbgl/vulkan/renderable_resource.hpp | 2 +- include/mbgl/vulkan/renderer_backend.hpp | 2 +- .../activity/stability/NavigationMap.kt | 1 + platform/glfw/glfw_vulkan_backend.cpp | 4 +- .../render_location_indicator_layer.cpp | 10 +- src/mbgl/vulkan/context.cpp | 27 ++++-- src/mbgl/vulkan/renderable_resource.cpp | 13 ++- src/mbgl/vulkan/renderer_backend.cpp | 5 +- src/mbgl/vulkan/texture2d.cpp | 92 ++++++++----------- 13 files changed, 93 insertions(+), 86 deletions(-) diff --git a/include/mbgl/shaders/layer_ubo.hpp b/include/mbgl/shaders/layer_ubo.hpp index c9bb02036..73a96b8fb 100644 --- a/include/mbgl/shaders/layer_ubo.hpp +++ b/include/mbgl/shaders/layer_ubo.hpp @@ -52,11 +52,10 @@ static_assert(sizeof(GlobalPaintParamsUBO) == 3 * 16); #if MLN_RENDER_BACKEND_VULKAN struct alignas(16) GlobalPlatformParamsUBO { - /* 0 */ alignas(16) std::array rotation0; - /* 16 */ alignas(16) std::array rotation1; - /* 32 */ + /* 0 */ std::array surfaceRotation; + /* 16 */ }; -static_assert(sizeof(GlobalPlatformParamsUBO) == 2 * 16); +static_assert(sizeof(GlobalPlatformParamsUBO) == 16); #endif enum { diff --git a/include/mbgl/shaders/vulkan/common.hpp b/include/mbgl/shaders/vulkan/common.hpp index 4a33aa5de..dd3d2267b 100644 --- a/include/mbgl/shaders/vulkan/common.hpp +++ b/include/mbgl/shaders/vulkan/common.hpp @@ -109,13 +109,17 @@ layout(set = GLOBAL_SET_INDEX, binding = 0) uniform GlobalPaintParamsUBO { #ifdef USE_SURFACE_TRANSFORM layout(set = GLOBAL_SET_INDEX, binding = 1) uniform GlobalPlatformParamsUBO { - mat2 rotation; + vec4 surfaceRotation; } platformParams; #endif void applySurfaceTransform() { #ifdef USE_SURFACE_TRANSFORM - gl_Position.xy = platformParams.rotation * gl_Position.xy; + const mat2 rotation = { + platformParams.surfaceRotation.xy, + platformParams.surfaceRotation.zw + }; + gl_Position.xy = rotation * gl_Position.xy; #endif gl_Position.y *= -1.0; diff --git a/include/mbgl/shaders/vulkan/location_indicator.hpp b/include/mbgl/shaders/vulkan/location_indicator.hpp index 0376bd785..81acf806b 100644 --- a/include/mbgl/shaders/vulkan/location_indicator.hpp +++ b/include/mbgl/shaders/vulkan/location_indicator.hpp @@ -80,7 +80,11 @@ layout(location = 0) out vec4 out_color; layout(set = DRAWABLE_IMAGE_SET_INDEX, binding = 0) uniform sampler2D image_sampler; void main() { - out_color = texture(image_sampler, frag_uv); + // using lod bias variant for sampling as a workaround for Adreno 600 series + out_color = texture(image_sampler, frag_uv, 0.0); + // working alternatives + //out_color = textureGrad(image_sampler, frag_uv, dFdx(frag_uv), dFdy(frag_uv)); + //out_color = texelFetch(image_sampler, ivec2(frag_uv * vec2(textureSize(image_sampler, 0))), 0); } )"; }; diff --git a/include/mbgl/vulkan/context.hpp b/include/mbgl/vulkan/context.hpp index 357caaf99..5dbb21891 100644 --- a/include/mbgl/vulkan/context.hpp +++ b/include/mbgl/vulkan/context.hpp @@ -60,7 +60,7 @@ public: void beginFrame() override; void endFrame() override; void submitFrame(); - void waitFrame() const; + bool waitFrame() const; std::unique_ptr createCommandEncoder() override; diff --git a/include/mbgl/vulkan/renderable_resource.hpp b/include/mbgl/vulkan/renderable_resource.hpp index fa5772b94..dc8765469 100644 --- a/include/mbgl/vulkan/renderable_resource.hpp +++ b/include/mbgl/vulkan/renderable_resource.hpp @@ -49,7 +49,6 @@ protected: void setDepthFormat(vk::Format format); void copySurfaceToReadTexture(); - void swap() override; public: virtual void createPlatformSurface() = 0; @@ -78,6 +77,7 @@ public: void init(uint32_t w, uint32_t h); void recreateSwapchain(); + void swap() override; void queueSurfaceRead(); std::shared_ptr readImage(); diff --git a/include/mbgl/vulkan/renderer_backend.hpp b/include/mbgl/vulkan/renderer_backend.hpp index 295fc5862..c96e765cd 100644 --- a/include/mbgl/vulkan/renderer_backend.hpp +++ b/include/mbgl/vulkan/renderer_backend.hpp @@ -21,7 +21,7 @@ #if !defined(NDEBUG) && !defined(__ANDROID__) #define ENABLE_VULKAN_VALIDATION -// #define ENABLE_VULKAN_GPU_ASSISTED_VALIDATION +// #define ENABLE_VULKAN_VALIDATION_FEATURES // #define ENABLE_VMA_DEBUG #endif diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/stability/NavigationMap.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/stability/NavigationMap.kt index c83c548fa..d09b4956f 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/stability/NavigationMap.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/stability/NavigationMap.kt @@ -175,6 +175,7 @@ class NavigationMap : SupportMapFragment(), ProgressChangeListener, MilestoneEve LocationComponentActivationOptions .builder(requireContext(), map.style!!) .useDefaultLocationEngine(false) + .useSpecializedLocationLayer(true) .build() ) diff --git a/platform/glfw/glfw_vulkan_backend.cpp b/platform/glfw/glfw_vulkan_backend.cpp index 536a202c2..650ff38a8 100644 --- a/platform/glfw/glfw_vulkan_backend.cpp +++ b/platform/glfw/glfw_vulkan_backend.cpp @@ -154,9 +154,7 @@ GLFWVulkanBackend::GLFWVulkanBackend(GLFWwindow* window_, const bool capFrameRat init(); } -GLFWVulkanBackend::~GLFWVulkanBackend() { - context.reset(); -} +GLFWVulkanBackend::~GLFWVulkanBackend() {} mbgl::Size GLFWVulkanBackend::getSize() const { return size; diff --git a/src/mbgl/renderer/layers/render_location_indicator_layer.cpp b/src/mbgl/renderer/layers/render_location_indicator_layer.cpp index cc5be175f..5cc86c3dc 100644 --- a/src/mbgl/renderer/layers/render_location_indicator_layer.cpp +++ b/src/mbgl/renderer/layers/render_location_indicator_layer.cpp @@ -867,9 +867,9 @@ protected: mapbox::cheap_ruler::CheapRuler ruler; - mbgl::mat4 translation; - mbgl::mat4 projectionCircle; - mbgl::mat4 projectionPuck; + mbgl::mat4 translation{}; + mbgl::mat4 projectionCircle{}; + mbgl::mat4 projectionPuck{}; bool positionChanged = false; bool radiusChanged = false; @@ -885,7 +885,7 @@ public: std::optional> drawable; std::array geometry; TextureInfo textureInfo; - bool dirty{false}; + bool dirty{true}; gfx::Drawable& getDrawable() { return drawable.value().get(); } void reset() { textureInfo.reset(); } @@ -896,7 +896,7 @@ public: std::optional> outlineDrawable; std::array geometry; TextureInfo textureInfo; - bool dirty{false}; + bool dirty{true}; gfx::Drawable& getDrawable() { return drawable.value().get(); } void reset() { textureInfo.reset(); } diff --git a/src/mbgl/vulkan/context.cpp b/src/mbgl/vulkan/context.cpp index e1abffdde..c078cb9cc 100644 --- a/src/mbgl/vulkan/context.cpp +++ b/src/mbgl/vulkan/context.cpp @@ -36,9 +36,9 @@ namespace vulkan { // this can be queried at runtime (VkPhysicalDeviceLimits.maxVertexInputBindings) constexpr uint32_t maximumVertexBindingCount = 16; -constexpr uint32_t globalDescriptorPoolSize = 3 * 4; -constexpr uint32_t layerDescriptorPoolSize = 3 * 256; -constexpr uint32_t drawableUniformDescriptorPoolSize = 3 * 1024; +constexpr uint32_t globalDescriptorPoolSize = 2 * 1; +constexpr uint32_t layerDescriptorPoolSize = 2 * 256; +constexpr uint32_t drawableUniformDescriptorPoolSize = 2 * 1024; constexpr uint32_t drawableImageDescriptorPoolSize = drawableUniformDescriptorPoolSize / 2; namespace { @@ -236,7 +236,7 @@ void Context::requestSurfaceUpdate(bool useDelay) { surfaceUpdateLatency = useDelay ? backend.getMaxFrames() * 3 : 0; } -void Context::waitFrame() const { +bool Context::waitFrame() const { MLN_TRACE_FUNC(); MBGL_VERIFY_THREAD(tid); @@ -249,7 +249,10 @@ void Context::waitFrame() const { 1, &frame.flightFrameFence.get(), VK_TRUE, timeout, dispatcher); if (waitFenceResult != vk::Result::eSuccess) { mbgl::Log::Error(mbgl::Event::Render, "Wait fence failed"); + return false; } + + return true; } void Context::beginFrame() { @@ -304,7 +307,9 @@ void Context::beginFrame() { auto& frame = frameResources[frameResourceIndex]; constexpr uint64_t timeout = std::numeric_limits::max(); - waitFrame(); + if (!waitFrame()) { + return; + } frame.runDeletionQueue(*this); @@ -323,6 +328,10 @@ void Context::beginFrame() { } else if (acquireImageResult.result == vk::Result::eSuboptimalKHR) { renderableResource.setAcquiredImageIndex(acquireImageResult.value); requestSurfaceUpdate(); + } else { + mbgl::Log::Error( + mbgl::Event::Render, + "acquireNextImageKHR result: " + std::to_string(static_cast(acquireImageResult.result))); } } catch (const vk::OutOfDateKHRError& e) { @@ -521,11 +530,11 @@ void Context::bindGlobalUniformBuffers(gfx::RenderPass& renderPass) const noexce auto& renderableResource = renderPassImpl.getDescriptor().renderable.getResource(); if (renderableResource.hasSurfaceTransformSupport()) { - float surfaceRotation = renderableResource.getRotation(); + const float surfaceRotation = renderableResource.getRotation(); + const float sinRot = std::sin(surfaceRotation); + const float cosRot = std::cos(surfaceRotation); - const shaders::GlobalPlatformParamsUBO platformUBO = { - /* .rotation0 = */ {cosf(surfaceRotation), -sinf(surfaceRotation)}, - /* .rotation1 = */ {sinf(surfaceRotation), cosf(surfaceRotation)}}; + const shaders::GlobalPlatformParamsUBO platformUBO = {.surfaceRotation = {cosRot, -sinRot, sinRot, cosRot}}; context.globalUniformBuffers.createOrUpdate( shaders::idGlobalPlatformParamsUBO, &platformUBO, sizeof(platformUBO), context); } diff --git a/src/mbgl/vulkan/renderable_resource.cpp b/src/mbgl/vulkan/renderable_resource.cpp index c114b69e2..eefa3bd47 100644 --- a/src/mbgl/vulkan/renderable_resource.cpp +++ b/src/mbgl/vulkan/renderable_resource.cpp @@ -173,15 +173,20 @@ void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) { setColorFormat(swapchainCreateInfo.imageFormat); extent = swapchainCreateInfo.imageExtent; - acquireSemaphores.reserve(swapchainImages.size()); + acquireSemaphores.reserve(backend.getMaxFrames()); + for (uint32_t index = 0; index < backend.getMaxFrames(); ++index) { + acquireSemaphores.emplace_back(device->createSemaphoreUnique({}, nullptr, dispatcher)); + + const auto indexStr = std::to_string(index); + backend.setDebugName(acquireSemaphores.back().get(), "AcquireSemaphore_" + indexStr); + } + presentSemaphores.reserve(swapchainImages.size()); for (uint32_t index = 0; index < swapchainImages.size(); ++index) { - acquireSemaphores.emplace_back(device->createSemaphoreUnique({}, nullptr, dispatcher)); presentSemaphores.emplace_back(device->createSemaphoreUnique({}, nullptr, dispatcher)); const auto indexStr = std::to_string(index); - backend.setDebugName(acquireSemaphores.back().get(), "PresentSemaphore_" + indexStr); - backend.setDebugName(presentSemaphores.back().get(), "AcquireSemaphore_" + indexStr); + backend.setDebugName(presentSemaphores.back().get(), "PresentSemaphore_" + indexStr); } } diff --git a/src/mbgl/vulkan/renderer_backend.cpp b/src/mbgl/vulkan/renderer_backend.cpp index dc92304c4..f7a2be978 100644 --- a/src/mbgl/vulkan/renderer_backend.cpp +++ b/src/mbgl/vulkan/renderer_backend.cpp @@ -437,13 +437,14 @@ void RendererBackend::initInstance() { const auto& debugExtensions = getDebugExtensions(); extensions.insert(extensions.end(), debugExtensions.begin(), debugExtensions.end()); -#ifdef ENABLE_VULKAN_GPU_ASSISTED_VALIDATION +#ifdef ENABLE_VULKAN_VALIDATION_FEATURES appInfo.setApiVersion(VK_API_VERSION_1_2); const std::vector validationFeatures = { vk::ValidationFeatureEnableEXT::eGpuAssisted, vk::ValidationFeatureEnableEXT::eGpuAssistedReserveBindingSlot, vk::ValidationFeatureEnableEXT::eSynchronizationValidation, + vk::ValidationFeatureEnableEXT::eBestPractices, }; const vk::ValidationFeaturesEXT validationFeatureInfo(validationFeatures); @@ -603,7 +604,7 @@ void RendererBackend::initDevice() { .setPEnabledExtensionNames(extensions) .setPEnabledFeatures(&physicalDeviceFeatures); -#ifdef ENABLE_VULKAN_GPU_ASSISTED_VALIDATION +#ifdef ENABLE_VULKAN_VALIDATION_FEATURES vk::PhysicalDeviceVulkan12Features extraFeatures; extraFeatures.setBufferDeviceAddress(true); extraFeatures.setTimelineSemaphore(true); diff --git a/src/mbgl/vulkan/texture2d.cpp b/src/mbgl/vulkan/texture2d.cpp index 6b7b69ed2..40bebda6f 100644 --- a/src/mbgl/vulkan/texture2d.cpp +++ b/src/mbgl/vulkan/texture2d.cpp @@ -406,8 +406,8 @@ void Texture2D::createSampler() { auto samplerCreateInfo = vk::SamplerCreateInfo() .setMinFilter(filter) .setMagFilter(filter) - .setMinLod(-VK_LOD_CLAMP_NONE) - .setMaxLod(VK_LOD_CLAMP_NONE) + .setMinLod(0.0f) + .setMaxLod(static_cast(getMipLevels() - 1)) .setAddressModeU(addressModeU) .setAddressModeV(addressModeV) .setAddressModeW(vk::SamplerAddressMode::eRepeat); @@ -417,7 +417,11 @@ void Texture2D::createSampler() { } if (samplerState.maxAnisotropy != 1 && backend.getDeviceFeatures().samplerAnisotropy) { - samplerCreateInfo.setAnisotropyEnable(true).setMaxAnisotropy(samplerState.maxAnisotropy); + const auto deviceMaxAnisotropy = static_cast( + backend.getDeviceProperties().limits.maxSamplerAnisotropy); + const auto maxAnisotropy = std::min(deviceMaxAnisotropy, samplerState.maxAnisotropy); + + samplerCreateInfo.setAnisotropyEnable(true).setMaxAnisotropy(maxAnisotropy); } sampler = backend.getDevice()->createSampler(samplerCreateInfo, nullptr, backend.getDispatcher()); @@ -506,15 +510,18 @@ void Texture2D::transitionToTransferReadLayout(const vk::UniqueCommandBuffer& bu } void Texture2D::transitionToShaderReadLayout(const vk::UniqueCommandBuffer& buffer) { + const auto srcAccessMask = imageLayout == vk::ImageLayout::eTransferSrcOptimal ? vk::AccessFlagBits::eTransferRead + : vk::AccessFlagBits::eTransferWrite; + const auto barrier = vk::ImageMemoryBarrier() .setImage(imageAllocation->image) .setOldLayout(imageLayout) .setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal) - .setSrcAccessMask(vk::AccessFlagBits::eTransferWrite) + .setSrcAccessMask(srcAccessMask) .setDstAccessMask(vk::AccessFlagBits::eShaderRead) .setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) .setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) - .setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}); + .setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, getMipLevels(), 0, 1}); buffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, @@ -756,6 +763,9 @@ void Texture2D::generateMips(const vk::UniqueCommandBuffer& buffer) { const auto& dispatcher = context.getBackend().getDispatcher(); + // transfer top level mip to read + transitionToTransferReadLayout(buffer); + int32_t mipWidth = size.width; int32_t mipHeight = size.height; @@ -766,9 +776,26 @@ void Texture2D::generateMips(const vk::UniqueCommandBuffer& buffer) { .setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}); for (uint32_t i = 1; i < mipLevels; ++i) { - barrier.subresourceRange.baseMipLevel = i - 1; + barrier.subresourceRange.baseMipLevel = i; - // flip transfer direction + const int32_t nextMipWidth = std::max(1, mipWidth / 2); + const int32_t nextMipHeight = std::max(1, mipHeight / 2); + + const auto blit = vk::ImageBlit() + .setSrcOffsets({vk::Offset3D{0, 0, 0}, {mipWidth, mipHeight, 1}}) + .setDstOffsets({vk::Offset3D{0, 0, 0}, {nextMipWidth, nextMipHeight, 1}}) + .setSrcSubresource({vk::ImageAspectFlagBits::eColor, i - 1, 0, 1}) + .setDstSubresource({vk::ImageAspectFlagBits::eColor, i, 0, 1}); + + buffer->blitImage(imageAllocation->image, + vk::ImageLayout::eTransferSrcOptimal, + imageAllocation->image, + vk::ImageLayout::eTransferDstOptimal, + blit, + vk::Filter::eLinear, + dispatcher); + + // prepare next loop read barrier.setOldLayout(vk::ImageLayout::eTransferDstOptimal) .setNewLayout(vk::ImageLayout::eTransferSrcOptimal) .setSrcAccessMask(vk::AccessFlagBits::eTransferWrite) @@ -782,54 +809,13 @@ void Texture2D::generateMips(const vk::UniqueCommandBuffer& buffer) { barrier, dispatcher); - const auto blit = vk::ImageBlit() - .setSrcOffsets({vk::Offset3D{0, 0, 0}, {mipWidth, mipHeight, 1}}) - .setDstOffsets({vk::Offset3D{0, 0, 0}, {mipWidth / 2, mipHeight / 2, 1}}) - .setSrcSubresource({vk::ImageAspectFlagBits::eColor, i - 1, 0, 1}) - .setDstSubresource({vk::ImageAspectFlagBits::eColor, i, 0, 1}); - - buffer->blitImage(imageAllocation->image, - barrier.newLayout, - imageAllocation->image, - barrier.oldLayout, - blit, - vk::Filter::eLinear, - dispatcher); - - barrier.setOldLayout(vk::ImageLayout::eTransferSrcOptimal) - .setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal) - .setSrcAccessMask(vk::AccessFlagBits::eTransferRead) - .setDstAccessMask(vk::AccessFlagBits::eShaderRead); - - buffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eFragmentShader, - {}, - nullptr, - nullptr, - barrier, - dispatcher); - - mipWidth = std::max(1, mipWidth / 2); - mipHeight = std::max(1, mipHeight / 2); + mipWidth = nextMipWidth; + mipHeight = nextMipHeight; } - // transition the last mip - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - - barrier.setOldLayout(vk::ImageLayout::eTransferDstOptimal) - .setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal) - .setSrcAccessMask(vk::AccessFlagBits::eTransferRead) - .setDstAccessMask(vk::AccessFlagBits::eShaderRead); - - buffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eFragmentShader, - {}, - nullptr, - nullptr, - barrier, - dispatcher); - - imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + // transition image to fragment read + imageLayout = vk::ImageLayout::eTransferSrcOptimal; + transitionToShaderReadLayout(buffer); } } // namespace vulkan