fix(renderer): vulkan rotation, mali triangle padding and wgsl bounds for script gpu passes (#14485) 4c2d79894f
* fix(renderer): let the compositor rotate our Vulkan swapchains

* fix(evi): rebuild the Vulkan swapchain when an Android surface rotates

* fix(ore): report half float render targets on Vulkan, D3D and WebGPU

* fix(renderer): enable robust buffer access so script shaders keep WGSL bounds

* fix(scripting): clamp out of range accesses in script shaders as WGSL requires

* fix(renderer): keep glsl texel fetches lowering, limit robust access to arm and surface polling to android

* fix(renderer): name the interior triangulation read as the reason for robust access on mali

* fix(renderer): zero the triangle buffer tail mali shades past a draw, drop robust access

* fix(renderer): start each flush's triangles on a mali vertex group of 4

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index 48e9d66..2ef8e90 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-4057f908b214181e224a7c9e9d9339641f6b7dc3
+4c2d79894f8dfd8dd8cca0aa6d4a44504cdc51b9
diff --git a/renderer/include/rive/renderer/gpu.hpp b/renderer/include/rive/renderer/gpu.hpp
index 9e09429..f3b6558 100644
--- a/renderer/include/rive/renderer/gpu.hpp
+++ b/renderer/include/rive/renderer/gpu.hpp
@@ -1886,6 +1886,10 @@
 };
 static_assert(sizeof(TriangleVertex) == sizeof(float) * 3);
 
+// Mali shades vertices in index aligned groups of this size, loads included, so
+// a draw also runs up to 3 vertices on either side of its range.
+constexpr static size_t kTriangleVertexGroupSize = 4;
+
 enum class BoundVertexInstanceType
 {
     none,
@@ -2154,6 +2158,12 @@
         }
     }
     void skip_back() { push(); }
+    void zero_unwritten()
+    {
+        memset(static_cast<void*>(m_nextMappedItem),
+               0,
+               (m_mappingEnd - m_nextMappedItem) * sizeof(T));
+    }
 
 private:
     RIVE_ALWAYS_INLINE T& push()
diff --git a/renderer/rive_vk_bootstrap/src/vulkan_swapchain.cpp b/renderer/rive_vk_bootstrap/src/vulkan_swapchain.cpp
index 258cd69..475d808 100644
--- a/renderer/rive_vk_bootstrap/src/vulkan_swapchain.cpp
+++ b/renderer/rive_vk_bootstrap/src/vulkan_swapchain.cpp
@@ -88,6 +88,13 @@
         device.getSurfaceCapabilities(surface, &surfaceCaps),
         "Failed to get Vulkan device surface capabilities");
 
+    // We never draw prerotated, so the compositor has to rotate our frames.
+    VkSurfaceTransformFlagBitsKHR preTransform =
+        (surfaceCaps.supportedTransforms &
+         VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
+            ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
+            : surfaceCaps.currentTransform;
+
     VkSwapchainCreateInfoKHR swapchainCreateInfo = {
         .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
         .surface = surface,
@@ -98,7 +105,7 @@
         .imageExtent = surfaceCaps.currentExtent,
         .imageArrayLayers = 1,
         .imageUsage = opts.imageUsageFlags,
-        .preTransform = surfaceCaps.currentTransform,
+        .preTransform = preTransform,
         .compositeAlpha = compositeAlphaFlags,
         .presentMode = presentMode.value(),
         .clipped = true,
diff --git a/renderer/src/ore/d3d11/ore_context_d3d11.cpp b/renderer/src/ore/d3d11/ore_context_d3d11.cpp
index 9d753e5..a1bdbb3 100644
--- a/renderer/src/ore/d3d11/ore_context_d3d11.cpp
+++ b/renderer/src/ore/d3d11/ore_context_d3d11.cpp
@@ -1527,6 +1527,7 @@
     // Populate features for D3D11 feature level 11.0.
     Features& f = ctx->m_features;
     f.colorBufferFloat = true;
+    f.colorBufferHalfFloat = true;
     f.perTargetBlend = true;
     f.perTargetWriteMask = true;
     f.textureViewSampling = true;
diff --git a/renderer/src/ore/d3d12/ore_context_d3d12.cpp b/renderer/src/ore/d3d12/ore_context_d3d12.cpp
index f46006f..3603d30 100644
--- a/renderer/src/ore/d3d12/ore_context_d3d12.cpp
+++ b/renderer/src/ore/d3d12/ore_context_d3d12.cpp
@@ -538,6 +538,7 @@
     // --- Features ---
     Features& f = ctx->m_features;
     f.colorBufferFloat = true;
+    f.colorBufferHalfFloat = true;
     f.perTargetBlend = true;
     f.perTargetWriteMask = true;
     f.textureViewSampling = true;
diff --git a/renderer/src/ore/vulkan/ore_context_vulkan.cpp b/renderer/src/ore/vulkan/ore_context_vulkan.cpp
index 7d40f29..61ca546 100644
--- a/renderer/src/ore/vulkan/ore_context_vulkan.cpp
+++ b/renderer/src/ore/vulkan/ore_context_vulkan.cpp
@@ -342,6 +342,7 @@
 
     Features& f = ctx->m_features;
     f.colorBufferFloat = true; // All Vulkan 1.1+ support rgba16f attachments.
+    f.colorBufferHalfFloat = true;
     f.perTargetBlend = enabled.independentBlend;
     f.perTargetWriteMask = enabled.independentBlend;
     f.textureViewSampling = true;
diff --git a/renderer/src/ore/wgpu/ore_context_wgpu.cpp b/renderer/src/ore/wgpu/ore_context_wgpu.cpp
index bab08d5..4c08b3f 100644
--- a/renderer/src/ore/wgpu/ore_context_wgpu.cpp
+++ b/renderer/src/ore/wgpu/ore_context_wgpu.cpp
@@ -454,6 +454,7 @@
     // Populate features with WebGPU-level capabilities.
     Features& f = ctx->m_features;
     f.colorBufferFloat = true;
+    f.colorBufferHalfFloat = true;
     f.perTargetBlend = true;
     f.perTargetWriteMask = true;
     f.textureViewSampling = true;
diff --git a/renderer/src/render_context.cpp b/renderer/src/render_context.cpp
index 57bcb58..ceac5c2 100644
--- a/renderer/src/render_context.cpp
+++ b/renderer/src/render_context.cpp
@@ -883,7 +883,11 @@
         .tessSpanBufferCount =
             totalFrameResourceCounts.maxTessellatedSegmentCount,
         .triangleVertexBufferCount =
-            totalFrameResourceCounts.maxTriangleVertexCount,
+            totalFrameResourceCounts.maxTriangleVertexCount > 0
+                ? totalFrameResourceCounts.maxTriangleVertexCount +
+                      (m_logicalFlushes.size() + 1) *
+                          (kTriangleVertexGroupSize - 1)
+                : 0,
         .imageRectInstanceBufferCount = totalFrameResourceCounts.imageRectCount,
         .imageMeshInstanceBufferCount = totalFrameResourceCounts.imageMeshCount,
         .gradTextureHeight = layoutCounts.maxGradTextureHeight,
@@ -1079,7 +1083,8 @@
         assert(m_tessSpanData.elementsWritten() <=
                totalFrameResourceCounts.maxTessellatedSegmentCount);
         assert(m_triangleVertexData.elementsWritten() <=
-               totalFrameResourceCounts.maxTriangleVertexCount);
+               totalFrameResourceCounts.maxTriangleVertexCount +
+                   m_logicalFlushes.size() * (kTriangleVertexGroupSize - 1));
 
         unmapResourceBuffers(resourceRequirements);
 
@@ -1533,6 +1538,18 @@
     // Exact tessSpan/triangleVertex counts aren't known until after their data
     // is written out.
     size_t firstTessVertexSpan = m_ctx->m_tessSpanData.elementsWritten();
+    if (m_ctx->m_triangleVertexData)
+    {
+        // Start on a vertex group, so neither this flush nor the one before it
+        // shades the other's vertices through the wrong path offsets.
+        const size_t groupPadding =
+            math::padding_to_align_up<kTriangleVertexGroupSize>(
+                m_ctx->m_triangleVertexData.elementsWritten());
+        for (size_t i = 0; i < groupPadding; ++i)
+        {
+            m_ctx->m_triangleVertexData.emplace_back(Vec2D{}, 0, 0);
+        }
+    }
     size_t initialTriangleVertexDataSize =
         m_ctx->m_triangleVertexData.bytesWritten();
 
@@ -3136,6 +3153,7 @@
     }
     if (m_triangleVertexData)
     {
+        m_triangleVertexData.zero_unwritten();
         m_triangleVertexData.unmapElements(
             m_impl.get(),
             &RenderContextImpl::unmapTriangleVertexBuffer,