blob: 290c321b9c8a66063f3a9c5a3c99b6dbb37d703a [file] [edit]
//
// Copyright 2026 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// VertexArrayMtl_unittest.mm: Unit tests for Metal vertex array draw command computation.
#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <cstddef>
#include <cstdint>
#include <limits>
#include <vector>
#include "libANGLE/renderer/metal/BufferMtl.h"
#include "libANGLE/renderer/metal/VertexArrayMtl.h"
namespace
{
using rx::BufferMtl;
using rx::DrawCommandRange;
using rx::DrawIndexRange;
inline std::vector<DrawCommandRange> ComputeDrawCommandRanges(
gl::PrimitiveMode mode,
uint32_t count,
size_t firstIndex,
const std::vector<DrawIndexRange> &drawIndexRanges,
gl::PrimitiveMode drawMode,
gl::DrawElementsType indexBufferType)
{
std::vector<DrawCommandRange> drawCommands;
rx::AppendSimpleDrawCommandRanges(drawCommands, mode, count, firstIndex, drawIndexRanges,
drawMode, indexBufferType);
return drawCommands;
}
TEST(ComputeDrawCommandRangesTest, UnsignedShortClientData)
{
std::vector<uint16_t> shortIndices = {0, 1, 2, 0xffff, 3, 4, 5};
auto ranges = BufferMtl::GetDrawIndexRangesFromClientData(
gl::DrawElementsType::UnsignedShort, static_cast<GLint>(shortIndices.size()),
shortIndices.data());
ASSERT_EQ(2u, ranges.size());
EXPECT_EQ(0u, ranges[0].begin);
EXPECT_EQ(2u, ranges[0].end);
EXPECT_EQ(4u, ranges[1].begin);
EXPECT_EQ(6u, ranges[1].end);
}
TEST(ComputeDrawCommandRangesTest, UnsignedIntClientData)
{
std::vector<uint32_t> intIndices = {0, 1, 2, 0xffffffff, 3, 4, 5};
auto ranges = BufferMtl::GetDrawIndexRangesFromClientData(gl::DrawElementsType::UnsignedInt,
static_cast<GLint>(intIndices.size()),
intIndices.data());
ASSERT_EQ(2u, ranges.size());
EXPECT_EQ(0u, ranges[0].begin);
EXPECT_EQ(2u, ranges[0].end);
EXPECT_EQ(4u, ranges[1].begin);
EXPECT_EQ(6u, ranges[1].end);
}
// Helper to build DrawIndexRanges using BufferMtl::GetDrawIndexRangesFromClientData.
// Values of 0xff are converted to 0xff (primitive restart marker for UnsignedByte).
std::vector<DrawIndexRange> MakeDrawIndexRanges(const std::vector<uint8_t> &indices)
{
return BufferMtl::GetDrawIndexRangesFromClientData(
gl::DrawElementsType::UnsignedByte, static_cast<GLint>(indices.size()), indices.data());
}
TEST(ComputeDrawCommandRangesTest, Triangles_NoRestart)
{
auto ranges = MakeDrawIndexRanges({0, 1, 2, 3, 4, 5});
ASSERT_EQ(1u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 6, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(6u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_SingleRestartMiddle)
{
// Buffer: 0 1 2 ff 3 4 5 -> DrawIndexRanges: (0,2), (4,6)
auto ranges = MakeDrawIndexRanges({0, 1, 2, 0xff, 3, 4, 5});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 7, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
EXPECT_EQ(3u, cmds[1].count);
EXPECT_EQ(8u, cmds[1].offset); // index 4 * 2
}
TEST(ComputeDrawCommandRangesTest, Triangles_RestartAtBegin)
{
// Buffer: ff 0 1 2 -> DrawIndexRanges: (1,3)
auto ranges = MakeDrawIndexRanges({0xff, 0, 1, 2});
ASSERT_EQ(1u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 4, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(2u, cmds[0].offset); // index 1 * 2
}
TEST(ComputeDrawCommandRangesTest, Triangles_RestartAtEnd)
{
// Buffer: 0 1 2 ff -> DrawIndexRanges: (0,2)
auto ranges = MakeDrawIndexRanges({0, 1, 2, 0xff});
ASSERT_EQ(1u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 4, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_ConsecutiveRestarts)
{
auto ranges = MakeDrawIndexRanges({0, 1, 2, 0xff, 0xff, 3, 4, 5});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 8, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
EXPECT_EQ(3u, cmds[1].count);
EXPECT_EQ(10u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_IncompletePrimitiveDropped)
{
auto ranges = MakeDrawIndexRanges({0, 1, 0xff, 2, 3, 4});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 6, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(6u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_WithOffset)
{
auto ranges = MakeDrawIndexRanges({9, 0, 1, 2, 0xff, 3, 4, 5});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 7, 1, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(2u, cmds[0].offset);
EXPECT_EQ(3u, cmds[1].count);
EXPECT_EQ(10u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, Lines_WithRestart)
{
auto ranges = MakeDrawIndexRanges({0, 1, 0xff, 2, 3, 4});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Lines, 6, 0, ranges, gl::PrimitiveMode::Lines,
gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(2u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
EXPECT_EQ(2u, cmds[1].count);
EXPECT_EQ(6u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, Points_WithRestart)
{
auto ranges = MakeDrawIndexRanges({0, 0xff, 1});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Points, 3, 0, ranges, gl::PrimitiveMode::Points,
gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(1u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
EXPECT_EQ(1u, cmds[1].count);
EXPECT_EQ(4u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, EmptyBuffer)
{
std::vector<DrawIndexRange> ranges;
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 0, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
EXPECT_EQ(0u, cmds.size());
}
TEST(ComputeDrawCommandRangesTest, AllRestarts)
{
auto ranges = MakeDrawIndexRanges({0xff, 0xff, 0xff});
EXPECT_EQ(0u, ranges.size());
}
TEST(ComputeDrawCommandRangesTest, Triangles_Uint32IndexType)
{
auto ranges = MakeDrawIndexRanges({0, 1, 2, 0xff, 3, 4, 5});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 7, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedInt);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
EXPECT_EQ(3u, cmds[1].count);
EXPECT_EQ(16u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_CountLimitsWindow)
{
auto ranges = MakeDrawIndexRanges({0, 1, 2, 3, 4, 5});
ASSERT_EQ(1u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 4, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_SplitContentWithRestartAtBegin)
{
auto ranges = MakeDrawIndexRanges({0xff, 0, 1, 2, 0xff, 2, 1, 3});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 8, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(2u, cmds[0].offset);
EXPECT_EQ(3u, cmds[1].count);
EXPECT_EQ(10u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_SplitContentWithRestartAtEnd)
{
auto ranges = MakeDrawIndexRanges({0, 1, 2, 0xff, 2, 1, 3, 0xff});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 8, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
EXPECT_EQ(3u, cmds[1].count);
EXPECT_EQ(8u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_DegenerateBeforeContent)
{
auto ranges = MakeDrawIndexRanges({0, 0xff, 0, 1, 2, 2, 1, 3});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 8, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(6u, cmds[0].count);
EXPECT_EQ(4u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, Triangles_WithAddOffsetDegenerateAndRestartAtBegin)
{
auto ranges = MakeDrawIndexRanges({9, 0xff, 0, 0xff, 0, 1, 2, 2, 1, 3});
ASSERT_EQ(3u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::Triangles, 9, 1, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(6u, cmds[0].count);
EXPECT_EQ(8u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, TriangleStrip_NoRestart)
{
// 4 source -> (4-2)*3 = 6 draw indices
auto ranges = MakeDrawIndexRanges({0, 1, 2, 3});
ASSERT_EQ(1u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::TriangleStrip, 4, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(6u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, TriangleStrip_WithRestart)
{
auto ranges = MakeDrawIndexRanges({0, 1, 2, 3, 0xff, 4, 5, 6, 7});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::TriangleStrip, 9, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(6u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
EXPECT_EQ(6u, cmds[1].count);
EXPECT_EQ(30u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, TriangleStrip_TooFewIndices)
{
auto ranges = MakeDrawIndexRanges({0, 1, 0xff, 2, 3, 4});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::TriangleStrip, 6, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(3u, cmds[0].count);
EXPECT_EQ(18u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, TriangleStrip_RestartAtBeginAndEnd)
{
auto ranges = MakeDrawIndexRanges({0xff, 0, 1, 2, 3, 0xff});
ASSERT_EQ(1u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::TriangleStrip, 6, 0, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(6u, cmds[0].count);
EXPECT_EQ(6u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, LineStrip_NoRestart)
{
auto ranges = MakeDrawIndexRanges({0, 1, 2, 3});
ASSERT_EQ(1u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::LineStrip, 4, 0, ranges,
gl::PrimitiveMode::Lines, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(6u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, LineStrip_WithRestart)
{
auto ranges = MakeDrawIndexRanges({0, 1, 2, 0xff, 3, 4, 5});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::LineStrip, 7, 0, ranges,
gl::PrimitiveMode::Lines, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(2u, cmds.size());
EXPECT_EQ(4u, cmds[0].count);
EXPECT_EQ(0u, cmds[0].offset);
EXPECT_EQ(4u, cmds[1].count);
EXPECT_EQ(16u, cmds[1].offset);
}
TEST(ComputeDrawCommandRangesTest, LineStrip_TooFewIndices)
{
auto ranges = MakeDrawIndexRanges({0, 0xff, 1, 2});
ASSERT_EQ(2u, ranges.size());
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::LineStrip, 4, 0, ranges,
gl::PrimitiveMode::Lines, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(2u, cmds[0].count);
EXPECT_EQ(8u, cmds[0].offset);
}
TEST(ComputeDrawCommandRangesTest, TriangleStrip_WithBufferOffset)
{
auto ranges = MakeDrawIndexRanges({9, 0, 1, 2, 3});
auto cmds =
ComputeDrawCommandRanges(gl::PrimitiveMode::TriangleStrip, 4, 1, ranges,
gl::PrimitiveMode::Triangles, gl::DrawElementsType::UnsignedShort);
ASSERT_EQ(1u, cmds.size());
EXPECT_EQ(6u, cmds[0].count);
EXPECT_EQ(6u, cmds[0].offset);
}
} // namespace