Fix resize desync between Android surface and renderable (#4328)

This commit is contained in:
Adrian Cojocaru
2026-06-05 19:59:55 +03:00
committed by GitHub
parent 269d1bc910
commit 8a393c5b2c
8 changed files with 222 additions and 75 deletions

View File

@@ -43,6 +43,9 @@ protected:
void initSwapchain(uint32_t w, uint32_t h);
void initDepthStencil();
void initRenderPass();
void setColorFormat(vk::Format format);
void setDepthFormat(vk::Format format);
void swap() override;

View File

@@ -77,8 +77,7 @@ std::vector<const char*> AndroidVulkanRendererBackend::getInstanceExtensions() {
return extensions;
}
void AndroidVulkanRendererBackend::resizeFramebuffer(int width, int height) {
size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
void AndroidVulkanRendererBackend::resizeFramebuffer(int, int) {
if (context) {
static_cast<vulkan::Context&>(*context).requestSurfaceUpdate();
}

View File

@@ -1,9 +1,9 @@
package org.maplibre.android.testapp.activity.surfaceview
import android.animation.ValueAnimator
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import org.maplibre.android.maps.*
import org.maplibre.android.testapp.R
@@ -14,6 +14,8 @@ import org.maplibre.android.testapp.styles.TestStyles
*/
class SurfaceViewResizeActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private var animatedValue: ValueAnimator? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_surfaceview_resize)
@@ -37,18 +39,61 @@ class SurfaceViewResizeActivity : AppCompatActivity() {
}
private fun setupMap(maplibreMap: MapLibreMap) {
maplibreMap.setStyle(TestStyles.AWS_OPEN_DATA_STANDARD_LIGHT)
maplibreMap.setStyle(TestStyles.getPredefinedStyleWithFallback("Streets"))
}
private fun setupFab() {
val fabDebug = findViewById<FloatingActionButton>(R.id.fabResize)
fabDebug.setOnClickListener { view: View? ->
val parent = findViewById<View>(R.id.coordinator_layout)
val params = mapView.layoutParams
if (animatedValue != null) {
animatedValue?.cancel()
animatedValue = null
params.width = parent.width
params.height = parent.height
}
if (this::mapView.isInitialized) {
val parent = findViewById<View>(R.id.coordinator_layout)
val width = if (parent.width == mapView.width) parent.width / 2 else parent.width
val height =
if (parent.height == mapView.height) parent.height / 2 else parent.height
mapView.layoutParams = CoordinatorLayout.LayoutParams(width, height)
params.width = if (parent.width == params.width) parent.width / 2 else parent.width
params.height =
if (parent.height == params.height) parent.height / 2 else parent.height
}
mapView.requestLayout()
}
val fabAnimatedDebug = findViewById<FloatingActionButton>(R.id.fabAnimatedResize)
fabAnimatedDebug.setOnClickListener { view: View? ->
val parent = findViewById<View>(R.id.coordinator_layout)
if (animatedValue != null) {
animatedValue?.cancel()
animatedValue = null
mapView.layoutParams.width = parent.width
mapView.layoutParams.height = parent.height
mapView.requestLayout()
} else if (this::mapView.isInitialized) {
val minValue = parent.height / 2
val maxValue = parent.height
animatedValue = ValueAnimator.ofInt(maxValue, minValue)
animatedValue?.apply {
duration = 1000
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.REVERSE
addUpdateListener { animator ->
val height = animator.animatedValue as Int
mapView.layoutParams.height = height
mapView.requestLayout()
}
start()
}
}
}
}

View File

@@ -1,5 +1,6 @@
package org.maplibre.android.testapp.activity.textureview
import android.animation.ValueAnimator
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
@@ -14,6 +15,8 @@ import org.maplibre.android.testapp.styles.TestStyles
*/
class TextureViewResizeActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private var animatedValue: ValueAnimator? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_textureview_resize)
@@ -43,12 +46,55 @@ class TextureViewResizeActivity : AppCompatActivity() {
private fun setupFab() {
val fabDebug = findViewById<FloatingActionButton>(R.id.fabResize)
fabDebug.setOnClickListener { view: View? ->
val parent = findViewById<View>(R.id.coordinator_layout)
val params = mapView.layoutParams
if (animatedValue != null) {
animatedValue?.cancel()
animatedValue = null
params.width = parent.width
params.height = parent.height
}
if (this::mapView.isInitialized) {
val parent = findViewById<View>(R.id.coordinator_layout)
val width = if (parent.width == mapView.width) parent.width / 2 else parent.width
val height =
if (parent.height == mapView.height) parent.height / 2 else parent.height
mapView.layoutParams = CoordinatorLayout.LayoutParams(width, height)
params.width = if (parent.width == params.width) parent.width / 2 else parent.width
params.height =
if (parent.height == params.height) parent.height / 2 else parent.height
}
mapView.requestLayout()
}
val fabAnimatedDebug = findViewById<FloatingActionButton>(R.id.fabAnimatedResize)
fabAnimatedDebug.setOnClickListener { view: View? ->
val parent = findViewById<View>(R.id.coordinator_layout)
if (animatedValue != null) {
animatedValue?.cancel()
animatedValue = null
mapView.layoutParams.width = parent.width
mapView.layoutParams.height = parent.height
mapView.requestLayout()
} else if (this::mapView.isInitialized) {
val minValue = parent.height / 2
val maxValue = parent.height
animatedValue = ValueAnimator.ofInt(maxValue, minValue)
animatedValue?.apply {
duration = 1000
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.REVERSE
addUpdateListener { animator ->
val height = animator.animatedValue as Int
mapView.layoutParams.height = height
mapView.requestLayout()
}
start()
}
}
}
}

View File

@@ -14,6 +14,17 @@
android:layout_height="match_parent"
app:maplibre_renderTextureMode="false"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAnimatedResize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:layout_marginBottom="@dimen/fab_margin"
android:layout_marginLeft="@dimen/fab_margin"
android:src="@drawable/ic_animate_coordinates"
app:backgroundTint="@color/blueAccent"
app:layout_anchorGravity="bottom"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabResize"
android:layout_width="wrap_content"

View File

@@ -14,6 +14,17 @@
android:layout_height="match_parent"
app:maplibre_renderTextureMode="true"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAnimatedResize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:layout_marginBottom="@dimen/fab_margin"
android:layout_marginLeft="@dimen/fab_margin"
android:src="@drawable/ic_animate_coordinates"
app:backgroundTint="@color/blueAccent"
app:layout_anchorGravity="bottom"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabResize"
android:layout_width="wrap_content"

View File

@@ -18,7 +18,7 @@ import java.net.URL
*/
// Define the validation layer version, with a default value.
var vvlVersion = "1.4.341.0"
var vvlVersion = "1.4.350.0"
if (extra.has("vvl_version")) {
vvlVersion = extra["vvl_version"] as String
}

View File

@@ -31,7 +31,7 @@ void SurfaceRenderableResource::initColor(uint32_t w, uint32_t h) {
colorAllocations.reserve(imageCount);
swapchainImages.reserve(imageCount);
colorFormat = vk::Format::eR8G8B8A8Unorm;
setColorFormat(vk::Format::eR8G8B8A8Unorm);
extent = vk::Extent2D(w, h);
const auto imageUsage = vk::ImageUsageFlags() | vk::ImageUsageFlagBits::eColorAttachment |
@@ -156,7 +156,7 @@ void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) {
swapchain = device->createSwapchainKHRUnique(swapchainCreateInfo, nullptr, dispatcher);
swapchainImages = device->getSwapchainImagesKHR(swapchain.get(), dispatcher);
colorFormat = swapchainCreateInfo.imageFormat;
setColorFormat(swapchainCreateInfo.imageFormat);
extent = swapchainCreateInfo.imageExtent;
acquireSemaphores.reserve(swapchainImages.size());
@@ -193,7 +193,7 @@ void SurfaceRenderableResource::initDepthStencil() {
return;
}
depthFormat = *formatIt;
setDepthFormat(*formatIt);
const auto imageUsage = vk::ImageUsageFlags() | vk::ImageUsageFlagBits::eDepthStencilAttachment |
vk::ImageUsageFlagBits::eTransientAttachment;
@@ -242,6 +242,94 @@ void SurfaceRenderableResource::initDepthStencil() {
backend.setDebugName(depthAllocation->imageView.get(), "SwapchainDepthImageView");
}
void SurfaceRenderableResource::initRenderPass() {
// The current render pass should be invalidated if:
// - color/depth format changes (use setColorFormat/setDepthFormat)
// - renderable resource type changes
// - this is done currently only by inheritance and it can't change during runtime
if (renderPass) {
return;
}
const auto& device = backend.getDevice();
const auto& dispatcher = backend.getDispatcher();
const auto colorLayout = surface ? vk::ImageLayout::ePresentSrcKHR : vk::ImageLayout::eTransferSrcOptimal;
const std::array<vk::AttachmentDescription, 2> attachments = {
vk::AttachmentDescription()
.setFormat(colorFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eStore)
.setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(colorLayout),
vk::AttachmentDescription()
.setFormat(depthFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eDontCare)
.setStencilLoadOp(vk::AttachmentLoadOp::eClear)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal)};
const vk::AttachmentReference colorAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal);
const vk::AttachmentReference depthAttachmentRef(1, vk::ImageLayout::eDepthStencilAttachmentOptimal);
const auto subpass = vk::SubpassDescription()
.setPipelineBindPoint(vk::PipelineBindPoint::eGraphics)
.setColorAttachmentCount(1)
.setColorAttachments(colorAttachmentRef)
.setPDepthStencilAttachment(&depthAttachmentRef);
const std::array<vk::SubpassDependency, 2> dependencies = {
vk::SubpassDependency()
.setSrcSubpass(VK_SUBPASS_EXTERNAL)
.setDstSubpass(0)
.setSrcStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
.setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eColorAttachmentWrite),
vk::SubpassDependency()
.setSrcSubpass(VK_SUBPASS_EXTERNAL)
.setDstSubpass(0)
.setSrcStageMask(vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests)
.setDstStageMask(vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eDepthStencilAttachmentWrite),
};
const auto renderPassCreateInfo =
vk::RenderPassCreateInfo().setAttachments(attachments).setSubpasses(subpass).setDependencies(dependencies);
renderPass = device->createRenderPassUnique(renderPassCreateInfo, nullptr, dispatcher);
}
void SurfaceRenderableResource::setColorFormat(vk::Format format) {
if (colorFormat == format) {
return;
}
renderPass.reset();
colorFormat = format;
}
void SurfaceRenderableResource::setDepthFormat(vk::Format format) {
if (depthFormat == format) {
return;
}
renderPass.reset();
depthFormat = format;
}
void SurfaceRenderableResource::swap() {
auto& context = static_cast<Context&>(backend.getContext());
context.submitFrame();
@@ -327,62 +415,7 @@ void SurfaceRenderableResource::init(uint32_t w, uint32_t h) {
initDepthStencil();
// create render pass
const auto colorLayout = surface ? vk::ImageLayout::ePresentSrcKHR : vk::ImageLayout::eTransferSrcOptimal;
const std::array<vk::AttachmentDescription, 2> attachments = {
vk::AttachmentDescription()
.setFormat(colorFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eStore)
.setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(colorLayout),
vk::AttachmentDescription()
.setFormat(depthFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eDontCare)
.setStencilLoadOp(vk::AttachmentLoadOp::eClear)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal)};
const vk::AttachmentReference colorAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal);
const vk::AttachmentReference depthAttachmentRef(1, vk::ImageLayout::eDepthStencilAttachmentOptimal);
const auto subpass = vk::SubpassDescription()
.setPipelineBindPoint(vk::PipelineBindPoint::eGraphics)
.setColorAttachmentCount(1)
.setColorAttachments(colorAttachmentRef)
.setPDepthStencilAttachment(&depthAttachmentRef);
const std::array<vk::SubpassDependency, 2> dependencies = {
vk::SubpassDependency()
.setSrcSubpass(VK_SUBPASS_EXTERNAL)
.setDstSubpass(0)
.setSrcStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
.setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eColorAttachmentWrite),
vk::SubpassDependency()
.setSrcSubpass(VK_SUBPASS_EXTERNAL)
.setDstSubpass(0)
.setSrcStageMask(vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests)
.setDstStageMask(vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eDepthStencilAttachmentWrite),
};
const auto renderPassCreateInfo =
vk::RenderPassCreateInfo().setAttachments(attachments).setSubpasses(subpass).setDependencies(dependencies);
renderPass = device->createRenderPassUnique(renderPassCreateInfo, nullptr, dispatcher);
initRenderPass();
// create swapchain framebuffers
swapchainFramebuffers.reserve(swapchainImageViews.size());
@@ -408,7 +441,6 @@ void SurfaceRenderableResource::recreateSwapchain() {
backend.getDevice()->waitIdle(backend.getDispatcher());
swapchainFramebuffers.clear();
renderPass.reset();
swapchainImageViews.clear();
swapchainImages.clear();
acquireSemaphores.clear();