fix(android): Workaround for Adreno VK_ERROR_DEVICE_LOST (#4442)

This commit is contained in:
Adrian Cojocaru
2026-08-12 12:21:24 +03:00
committed by GitHub
parent 8be081ae83
commit bd92c87d39
13 changed files with 93 additions and 86 deletions

View File

@@ -52,11 +52,10 @@ static_assert(sizeof(GlobalPaintParamsUBO) == 3 * 16);
#if MLN_RENDER_BACKEND_VULKAN
struct alignas(16) GlobalPlatformParamsUBO {
/* 0 */ alignas(16) std::array<float, 2> rotation0;
/* 16 */ alignas(16) std::array<float, 2> rotation1;
/* 32 */
/* 0 */ std::array<float, 4> surfaceRotation;
/* 16 */
};
static_assert(sizeof(GlobalPlatformParamsUBO) == 2 * 16);
static_assert(sizeof(GlobalPlatformParamsUBO) == 16);
#endif
enum {

View File

@@ -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;

View File

@@ -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);
}
)";
};

View File

@@ -60,7 +60,7 @@ public:
void beginFrame() override;
void endFrame() override;
void submitFrame();
void waitFrame() const;
bool waitFrame() const;
std::unique_ptr<gfx::CommandEncoder> createCommandEncoder() override;

View File

@@ -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<PremultipliedImage> readImage();

View File

@@ -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

View File

@@ -175,6 +175,7 @@ class NavigationMap : SupportMapFragment(), ProgressChangeListener, MilestoneEve
LocationComponentActivationOptions
.builder(requireContext(), map.style!!)
.useDefaultLocationEngine(false)
.useSpecializedLocationLayer(true)
.build()
)

View File

@@ -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;

View File

@@ -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<std::reference_wrapper<gfx::Drawable>> drawable;
std::array<vec2, 4> 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<std::reference_wrapper<gfx::Drawable>> outlineDrawable;
std::array<vec2, 73> geometry;
TextureInfo textureInfo;
bool dirty{false};
bool dirty{true};
gfx::Drawable& getDrawable() { return drawable.value().get(); }
void reset() { textureInfo.reset(); }

View File

@@ -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<uint64_t>::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<int>(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<SurfaceRenderableResource>();
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);
}

View File

@@ -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);
}
}

View File

@@ -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<vk::ValidationFeatureEnableEXT> 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);

View File

@@ -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<float>(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<uint8_t>(
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