Account for vec3 padding in Metal (#181563)

Re-landing of https://github.com/flutter/flutter/pull/181340
incorporating https://github.com/flutter/flutter/pull/181550.

Vec3 uniforms on the metal backend are vec4-aligned. This PR accounts
for that padding.

---------

Co-authored-by: gaaclarke <30870216+gaaclarke@users.noreply.github.com>
This commit is contained in:
walley892 2026-01-28 12:50:23 -08:00 committed by GitHub
parent b6fa57dd2e
commit e435edfbcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 233 additions and 138 deletions

View File

@ -106,6 +106,7 @@ bool CompilerTestBase::CanCompileAndReflect(
entry_point_name);
Reflector::Options reflector_options;
reflector_options.target_platform = GetParam();
reflector_options.header_file_name = ReflectionHeaderName(fixture_name);
reflector_options.shader_name = "shader_name";

View File

@ -26,6 +26,7 @@
#include "impeller/geometry/matrix.h"
#include "impeller/geometry/scalar.h"
#include "impeller/runtime_stage/runtime_stage.h"
#include "runtime_stage_types_flatbuffers.h"
#include "spirv_common.hpp"
namespace impeller {
@ -372,6 +373,27 @@ std::shared_ptr<RuntimeStageData::Shader> Reflector::GenerateRuntimeStageData()
uniform_description.columns = spir_type.columns;
uniform_description.bit_width = spir_type.width;
uniform_description.array_elements = GetArrayElements(spir_type);
if (TargetPlatformIsMetal(options_.target_platform) &&
uniform_description.type == spirv_cross::SPIRType::BaseType::Float) {
// Metal aligns float3 to 16 bytes.
// Metal aligns float3x3 COLUMNS to 16 bytes.
// For float3: Size 12. Padding 4. Stride 16.
// For float3x3: Size 36. Padding 12 (4 per col). Stride 48.
if (spir_type.vecsize == 3 &&
(spir_type.columns == 1 || spir_type.columns == 3)) {
for (size_t c = 0; c < spir_type.columns; c++) {
for (size_t v = 0; v < 3; v++) {
uniform_description.padding_layout.push_back(
fb::PaddingType::kFloat);
}
uniform_description.padding_layout.push_back(
fb::PaddingType::kPadding);
}
}
}
FML_CHECK(data->backend != RuntimeStageBackend::kVulkan ||
spir_type.basetype ==
spirv_cross::SPIRType::BaseType::SampledImage)
@ -396,7 +418,7 @@ std::shared_ptr<RuntimeStageData::Shader> Reflector::GenerateRuntimeStageData()
size_t binding =
compiler_->get_decoration(ubo.id, spv::Decoration::DecorationBinding);
auto members = ReadStructMembers(ubo.type_id);
std::vector<uint8_t> struct_layout;
std::vector<fb::PaddingType> padding_layout;
size_t float_count = 0;
for (size_t i = 0; i < members.size(); i += 1) {
@ -407,7 +429,7 @@ std::shared_ptr<RuntimeStageData::Shader> Reflector::GenerateRuntimeStageData()
size_t padding_count =
(member.size + sizeof(float) - 1) / sizeof(float);
while (padding_count > 0) {
struct_layout.push_back(0);
padding_layout.push_back(fb::PaddingType::kPadding);
padding_count--;
}
break;
@ -418,18 +440,18 @@ std::shared_ptr<RuntimeStageData::Shader> Reflector::GenerateRuntimeStageData()
// and 0 layout property per byte of padding
for (auto i = 0; i < member.array_elements; i++) {
for (auto j = 0u; j < member.size / sizeof(float); j++) {
struct_layout.push_back(1);
padding_layout.push_back(fb::PaddingType::kFloat);
}
for (auto j = 0u; j < member.element_padding / sizeof(float);
j++) {
struct_layout.push_back(0);
padding_layout.push_back(fb::PaddingType::kPadding);
}
}
} else {
size_t member_float_count = member.byte_length / sizeof(float);
float_count += member_float_count;
while (member_float_count > 0) {
struct_layout.push_back(1);
padding_layout.push_back(fb::PaddingType::kFloat);
member_float_count--;
}
}
@ -446,7 +468,7 @@ std::shared_ptr<RuntimeStageData::Shader> Reflector::GenerateRuntimeStageData()
.location = binding,
.binding = binding,
.type = spirv_cross::SPIRType::Struct,
.struct_layout = std::move(struct_layout),
.padding_layout = std::move(padding_layout),
.struct_float_count = float_count,
});
}

View File

@ -332,8 +332,8 @@ std::unique_ptr<fb::RuntimeStageT> RuntimeStageData::CreateStageFlatbuffer(
desc->array_elements = uniform.array_elements.value();
}
for (const auto& byte_type : uniform.struct_layout) {
desc->struct_layout.push_back(static_cast<fb::StructByteType>(byte_type));
for (const auto& byte_type : uniform.padding_layout) {
desc->padding_layout.push_back(static_cast<fb::PaddingType>(byte_type));
}
desc->struct_float_count = uniform.struct_float_count;

View File

@ -12,6 +12,7 @@
#include <optional>
#include <string>
#include "runtime_stage_types_flatbuffers.h"
#include "shaderc/shaderc.hpp"
#include "spirv_cross.hpp"
#include "spirv_msl.hpp"
@ -55,7 +56,11 @@ struct UniformDescription {
size_t columns = 0u;
size_t bit_width = 0u;
std::optional<size_t> array_elements = std::nullopt;
std::vector<uint8_t> struct_layout = {};
/// The layout of padding bytes in the uniform buffer.
/// The format matches the values in the flatbuffer
/// UniformDescription::padding_layout.
/// \see RuntimeEffectContents::EmplaceUniform
std::vector<fb::PaddingType> padding_layout = {};
size_t struct_float_count = 0u;
};

View File

@ -6,14 +6,37 @@
namespace impeller {
size_t RuntimeUniformDescription::GetSize() const {
size_t size = dimensions.rows * dimensions.cols * bit_width / 8u;
size_t RuntimeUniformDescription::GetDartSize() const {
size_t size = 0;
if (!padding_layout.empty()) {
for (impeller::RuntimePaddingType byte_type : padding_layout) {
if (byte_type == RuntimePaddingType::kFloat) {
size += sizeof(float);
}
}
} else {
size = dimensions.rows * dimensions.cols * bit_width / 8u;
}
if (array_elements.value_or(0) > 0) {
// Covered by check on the line above.
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
size *= array_elements.value();
}
return size;
}
size_t RuntimeUniformDescription::GetGPUSize() const {
size_t size = 0;
if (padding_layout.empty()) {
size = dimensions.rows * dimensions.cols * bit_width / 8u;
} else {
size = sizeof(float) * padding_layout.size();
}
if (array_elements.value_or(0) > 0) {
// Covered by check on the line above.
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
size *= array_elements.value();
}
size += sizeof(float) * struct_layout.size();
return size;
}

View File

@ -38,6 +38,11 @@ struct RuntimeUniformDimensions {
size_t cols = 0;
};
enum class RuntimePaddingType : uint8_t {
kPadding = 0,
kFloat = 1,
};
struct RuntimeUniformDescription {
std::string name;
size_t location = 0u;
@ -47,11 +52,16 @@ struct RuntimeUniformDescription {
RuntimeUniformDimensions dimensions = {};
size_t bit_width = 0u;
std::optional<size_t> array_elements;
std::vector<uint8_t> struct_layout = {};
std::vector<RuntimePaddingType> padding_layout = {};
size_t struct_float_count = 0u;
/// @brief Computes the total number of bytes that this uniform requires.
size_t GetSize() const;
/// @brief Computes the total number of bytes that this uniform requires for
/// representation in the Dart float buffer.
size_t GetDartSize() const;
/// @brief Computes the total number of bytes that this uniform requires for
/// representation in the GPU.
size_t GetGPUSize() const;
};
} // namespace impeller

View File

@ -38,7 +38,7 @@ size_t DlRuntimeEffectImpeller::uniform_size() const {
size_t total = 0;
for (const auto& uniform : runtime_stage_->GetUniforms()) {
total += uniform.GetSize();
total += uniform.GetGPUSize();
}
return total;
}

View File

@ -24,35 +24,48 @@
namespace impeller {
namespace {
constexpr char kPaddingType = 0;
constexpr char kFloatType = 1;
} // namespace
// static
BufferView RuntimeEffectContents::EmplaceVulkanUniform(
const std::shared_ptr<const std::vector<uint8_t>>& input_data,
BufferView RuntimeEffectContents::EmplaceUniform(
const uint8_t* source_data,
HostBuffer& data_host_buffer,
const RuntimeUniformDescription& uniform,
size_t minimum_uniform_alignment) {
// TODO(jonahwilliams): rewrite this to emplace directly into
// HostBuffer.
std::vector<float> uniform_buffer;
uniform_buffer.reserve(uniform.struct_layout.size());
size_t uniform_byte_index = 0u;
for (char byte_type : uniform.struct_layout) {
if (byte_type == kPaddingType) {
uniform_buffer.push_back(0.f);
} else {
FML_DCHECK(byte_type == kFloatType);
uniform_buffer.push_back(reinterpret_cast<const float*>(
input_data->data())[uniform_byte_index++]);
}
const RuntimeUniformDescription& uniform) {
size_t minimum_uniform_alignment =
data_host_buffer.GetMinimumUniformAlignment();
size_t alignment = std::max(uniform.bit_width / 8, minimum_uniform_alignment);
if (uniform.padding_layout.empty()) {
return data_host_buffer.Emplace(source_data, uniform.GetGPUSize(),
alignment);
}
// If the uniform has a padding layout, we need to repack the data.
// We can do this by using the EmplaceProc to write directly to the
// HostBuffer.
return data_host_buffer.Emplace(
reinterpret_cast<const void*>(uniform_buffer.data()),
sizeof(float) * uniform_buffer.size(), minimum_uniform_alignment);
uniform.GetGPUSize(), alignment,
[&uniform, source_data](uint8_t* destination) {
size_t count = uniform.array_elements.value_or(1);
if (count == 0) {
// Make sure to run at least once.
count = 1;
}
size_t uniform_byte_index = 0u;
size_t struct_float_index = 0u;
auto* float_destination = reinterpret_cast<float*>(destination);
auto* float_source = reinterpret_cast<const float*>(source_data);
for (size_t i = 0; i < count; i++) {
for (RuntimePaddingType byte_type : uniform.padding_layout) {
if (byte_type == RuntimePaddingType::kPadding) {
float_destination[struct_float_index++] = 0.f;
} else {
FML_DCHECK(byte_type == RuntimePaddingType::kFloat);
float_destination[struct_float_index++] =
float_source[uniform_byte_index++];
}
}
}
});
}
void RuntimeEffectContents::SetRuntimeStage(
@ -284,12 +297,8 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
<< "Uniform " << uniform.name
<< " had unexpected type kFloat for Vulkan backend.";
size_t alignment =
std::max(uniform.bit_width / 8,
data_host_buffer.GetMinimumUniformAlignment());
BufferView buffer_view =
data_host_buffer.Emplace(uniform_data_->data() + buffer_offset,
uniform.GetSize(), alignment);
BufferView buffer_view = EmplaceUniform(
uniform_data_->data() + buffer_offset, data_host_buffer, uniform);
ShaderUniformSlot uniform_slot;
uniform_slot.name = uniform.name.c_str();
@ -298,7 +307,7 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
DescriptorType::kUniformBuffer, uniform_slot,
std::move(metadata), std::move(buffer_view));
buffer_index++;
buffer_offset += uniform.GetSize();
buffer_offset += uniform.GetDartSize();
buffer_location++;
break;
}
@ -309,12 +318,10 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
uniform_slot.binding = uniform.location;
uniform_slot.name = uniform.name.c_str();
pass.BindResource(ShaderStage::kFragment,
DescriptorType::kUniformBuffer, uniform_slot,
nullptr,
EmplaceVulkanUniform(
uniform_data_, data_host_buffer, uniform,
data_host_buffer.GetMinimumUniformAlignment()));
pass.BindResource(
ShaderStage::kFragment, DescriptorType::kUniformBuffer,
uniform_slot, nullptr,
EmplaceUniform(uniform_data_->data(), data_host_buffer, uniform));
}
}
}

View File

@ -37,11 +37,17 @@ class RuntimeEffectContents final : public ColorSourceContents {
bool BootstrapShader(const ContentContext& renderer) const;
// Visible for testing
static BufferView EmplaceVulkanUniform(
const std::shared_ptr<const std::vector<uint8_t>>& input_data,
HostBuffer& host_buffer,
const RuntimeUniformDescription& uniform,
size_t minimum_uniform_alignment);
/// Copies the uniform data into the host buffer.
///
/// If the `uniform` has a `padding_layout`, it is used to repack the data.
///
/// @param source_data The pointer to the start of the uniform data in the
/// source.
/// @param host_buffer The host buffer to emplace the uniform data into.
/// @param uniform The description of the uniform being emplaced.
static BufferView EmplaceUniform(const uint8_t* source_data,
HostBuffer& host_buffer,
const RuntimeUniformDescription& uniform);
private:
bool RegisterShader(const ContentContext& renderer) const;

View File

@ -1922,12 +1922,9 @@ TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) {
uniform_data->resize(sizeof(FragUniforms));
memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
auto buffer_view = RuntimeEffectContents::EmplaceVulkanUniform(
uniform_data, GetContentContext()->GetTransientsDataBuffer(),
runtime_stage->GetUniforms()[0],
GetContentContext()
->GetTransientsDataBuffer()
.GetMinimumUniformAlignment());
auto buffer_view = RuntimeEffectContents::EmplaceUniform(
uniform_data->data(), GetContentContext()->GetTransientsDataBuffer(),
runtime_stage->GetUniforms()[0]);
// 16 bytes:
// 8 bytes for iResolution

View File

@ -81,9 +81,18 @@ absl::StatusOr<RuntimeStage> RuntimeStage::Create(
static_cast<size_t>(i->rows()), static_cast<size_t>(i->columns())};
desc.bit_width = i->bit_width();
desc.array_elements = i->array_elements();
if (i->struct_layout()) {
for (const auto& byte_type : *i->struct_layout()) {
desc.struct_layout.push_back(static_cast<uint8_t>(byte_type));
if (i->padding_layout()) {
for (const auto& byte_type : *i->padding_layout()) {
impeller::RuntimePaddingType type;
switch (byte_type) {
case fb::PaddingType::kPadding:
type = impeller::RuntimePaddingType::kPadding;
break;
case fb::PaddingType::kFloat:
type = impeller::RuntimePaddingType::kFloat;
break;
}
desc.padding_layout.push_back(type);
}
}
desc.struct_float_count = i->struct_float_count();

View File

@ -8,7 +8,7 @@ namespace impeller.fb;
// incremented any time there is a change to this file which changes the
// resulting flat buffer format.
enum RuntimeStagesFormatVersion:uint32 {
kVersion = 1
kVersion = 2
}
enum Stage:byte {
@ -29,7 +29,7 @@ enum UniformDataType:uint32 {
// A struct is made up solely of 4 byte floats and 4-byte paddings between
// them.
// This enum describes whether a particular byte is a float or padding.
enum StructByteType:uint8 {
enum PaddingType:uint8 {
kPadding = 0,
kFloat = 1,
}
@ -43,7 +43,7 @@ table UniformDescription {
rows: uint64;
columns: uint64;
array_elements: uint64;
struct_layout: [StructByteType];
padding_layout: [PaddingType];
struct_float_count: uint64;
}

View File

@ -94,6 +94,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 0u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_alpha");
@ -102,6 +103,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 1u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_sparkle_color");
@ -110,6 +112,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 2u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_sparkle_alpha");
@ -118,6 +121,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 3u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_blur");
@ -126,6 +130,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 4u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_radius_scale");
@ -134,6 +139,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 6u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_max_radius");
@ -142,6 +148,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 7u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_resolution_scale");
@ -150,6 +157,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 8u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_noise_scale");
@ -158,6 +166,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 9u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_noise_phase");
@ -166,6 +175,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 10u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
@ -175,6 +185,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 11u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_circle2");
@ -183,6 +194,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 12u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_circle3");
@ -191,6 +203,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 13u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_rotation1");
@ -199,6 +212,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 14u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_rotation2");
@ -207,6 +221,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 15u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
{
auto uni = stage->GetUniform("u_rotation3");
@ -215,6 +230,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
EXPECT_EQ(uni->dimensions.cols, 1u);
EXPECT_EQ(uni->location, 16u);
EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
EXPECT_TRUE(uni->padding_layout.empty());
}
break;
}
@ -234,14 +250,15 @@ TEST_P(RuntimeStageTest, CanReadUniforms) {
// introduced.
// This means 36 * 4 = 144 bytes total.
EXPECT_EQ(uni->GetSize(), 144u);
std::vector<uint8_t> layout(uni->GetSize() / sizeof(float), 1);
layout[5] = 0;
layout[6] = 0;
layout[7] = 0;
layout[23] = 0;
EXPECT_EQ(uni->GetGPUSize(), 144u);
std::vector<RuntimePaddingType> layout(uni->GetGPUSize() / sizeof(float),
RuntimePaddingType::kFloat);
layout[5] = RuntimePaddingType::kPadding;
layout[6] = RuntimePaddingType::kPadding;
layout[7] = RuntimePaddingType::kPadding;
layout[23] = RuntimePaddingType::kPadding;
EXPECT_THAT(uni->struct_layout, ::testing::ElementsAreArray(layout));
EXPECT_THAT(uni->padding_layout, ::testing::ElementsAreArray(layout));
break;
}
}

View File

@ -82,7 +82,8 @@ Dart_Handle ConvertUniformDescriptionToMap(
FML_DCHECK(!Dart_IsError(result));
}
{ // 2
Dart_Handle size = Dart_NewIntegerFromUint64(uniform_description.GetSize());
Dart_Handle size =
Dart_NewIntegerFromUint64(uniform_description.GetDartSize());
FML_DCHECK(!Dart_IsError(size));
[[maybe_unused]] Dart_Handle result =
Dart_ListSetAt(keys, 2, Dart_NewStringFromCString("size"));
@ -161,7 +162,7 @@ std::string FragmentProgram::initFromAsset(const std::string& asset_name) {
impeller::RuntimeUniformType::kSampledImage) {
sampled_image_count++;
} else {
other_uniforms_bytes += uniform_description.GetSize();
other_uniforms_bytes += uniform_description.GetDartSize();
}
}

View File

@ -90,7 +90,7 @@ void main() async {
);
});
_runSkiaTest('FragmentProgram getUniformFloat offset overflow', () async {
test('FragmentProgram getUniformFloat offset overflow', () async {
expect(
() => shader.getUniformFloat('iVec2Uniform', 2),
throwsA(
@ -101,9 +101,9 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformFloat offset underflow', () async {
test('FragmentProgram getUniformFloat offset underflow', () async {
expect(
() => shader.getUniformFloat('iVec2Uniform', -1),
throwsA(
@ -114,14 +114,14 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformVec2', () async {
test('FragmentProgram getUniformVec2', () async {
final UniformVec2Slot slot = shader.getUniformVec2('iVec2Uniform');
slot.set(6.0, 7.0);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformVec2 wrong size', () async {
test('FragmentProgram getUniformVec2 wrong size', () async {
expect(
() => shader.getUniformVec2('iVec3Uniform'),
throwsA(
@ -142,14 +142,14 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformVec3', () async {
test('FragmentProgram getUniformVec3', () async {
final UniformVec3Slot slot = shader.getUniformVec3('iVec3Uniform');
slot.set(0.8, 0.1, 0.3);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformVec3 wrong size', () async {
test('FragmentProgram getUniformVec3 wrong size', () async {
expect(
() => shader.getUniformVec3('iVec2Uniform'),
throwsA(
@ -170,14 +170,14 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformVec4', () async {
test('FragmentProgram getUniformVec4', () async {
final UniformVec4Slot slot = shader.getUniformVec4('iVec4Uniform');
slot.set(11.0, 22.0, 19.0, 96.0);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformVec4 wrong size', () async {
test('FragmentProgram getUniformVec4 wrong size', () async {
expect(
() => shader.getUniformVec4('iVec3Uniform'),
throwsA(
@ -188,18 +188,18 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformArray float', () async {
test('FragmentProgram getUniformArray float', () async {
final UniformArray<UniformFloatSlot> slots = shader.getUniformFloatArray(
'iFloatArrayUniform',
);
expect(slots.length, 10);
slots[0].set(1.0);
slots[1].set(1.0);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformArray not found', () async {
test('FragmentProgram getUniformArray not found', () async {
expect(
() => shader.getUniformFloatArray('unknown'),
throwsA(
@ -210,15 +210,15 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformArrayVec2', () async {
test('FragmentProgram getUniformArrayVec2', () async {
final UniformArray<UniformVec2Slot> slots = shader.getUniformVec2Array('iVec2ArrayUniform');
expect(slots.length, 3);
slots[0].set(1.0, 1.0);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformArrayVec2 wrong type', () async {
test('FragmentProgram getUniformArrayVec2 wrong type', () async {
expect(
() => shader.getUniformVec2Array('iVec3ArrayUniform'),
throwsA(
@ -229,15 +229,15 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformArrayVec3', () async {
test('FragmentProgram getUniformArrayVec3', () async {
final UniformArray<UniformVec3Slot> slots = shader.getUniformVec3Array('iVec3ArrayUniform');
expect(slots.length, 3);
slots[0].set(1.0, 1.0, 1.0);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformArrayVec3 wrong type', () async {
test('FragmentProgram getUniformArrayVec3 wrong type', () async {
expect(
() => shader.getUniformVec3Array('iFloatArrayUniform'),
throwsA(
@ -248,15 +248,15 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformArrayVec4', () async {
test('FragmentProgram getUniformArrayVec4', () async {
final UniformArray<UniformVec4Slot> slots = shader.getUniformVec4Array('iVec4ArrayUniform');
expect(slots.length, 3);
slots[0].set(1.0, 1.0, 1.0, 1.0);
});
}, skip: !_isMacMetal());
_runSkiaTest('FragmentProgram getUniformArrayVec4 wrong type', () async {
test('FragmentProgram getUniformArrayVec4 wrong type', () async {
expect(
() => shader.getUniformVec4Array('iFloatArrayUniform'),
throwsA(
@ -267,7 +267,7 @@ void main() async {
),
),
);
});
}, skip: !_isMacMetal());
});
test('FragmentProgram getImageSampler', () async {
@ -695,39 +695,31 @@ void main() async {
shader.dispose();
});
test(
'FragmentShader shader with array uniforms renders correctly',
() async {
final FragmentProgram program = await FragmentProgram.fromAsset('uniform_arrays.frag.iplr');
test('FragmentShader shader with array uniforms renders correctly', () async {
final FragmentProgram program = await FragmentProgram.fromAsset('uniform_arrays.frag.iplr');
final FragmentShader shader = program.fragmentShader();
for (var i = 0; i < 20; i++) {
shader.setFloat(i, i.toDouble());
}
final FragmentShader shader = program.fragmentShader();
for (var i = 0; i < 20; i++) {
shader.setFloat(i, i.toDouble());
}
await _expectShaderRendersGreen(shader);
shader.dispose();
},
skip: Platform.executableArguments.contains('--impeller-backend=metal'),
);
await _expectShaderRendersGreen(shader);
shader.dispose();
});
test(
'FragmentShader shader with mat2 uniform renders correctly',
() async {
final FragmentProgram program = await FragmentProgram.fromAsset('uniform_mat2.frag.iplr');
test('FragmentShader shader with mat2 uniform renders correctly', () async {
final FragmentProgram program = await FragmentProgram.fromAsset('uniform_mat2.frag.iplr');
final FragmentShader shader = program.fragmentShader();
final FragmentShader shader = program.fragmentShader();
shader.setFloat(0, 4.0); // m00
shader.setFloat(1, 8.0); // m01
shader.setFloat(2, 16.0); // m10
shader.setFloat(3, 32.0); // m11
shader.setFloat(0, 4.0); // m00
shader.setFloat(1, 8.0); // m01
shader.setFloat(2, 16.0); // m10
shader.setFloat(3, 32.0); // m11
await _expectShaderRendersGreen(shader);
shader.dispose();
},
skip: Platform.executableArguments.contains('--impeller-backend=metal'),
);
await _expectShaderRendersGreen(shader);
shader.dispose();
});
_runImpellerTest(
'ImageFilter.shader errors if shader does not have correct uniform layout',
@ -878,6 +870,11 @@ void _runImpellerTest(String name, Future<void> Function() callback, {Object? sk
}, skip: skip);
}
// TODO(walley892): remove this function and associated test skips, https://github.com/flutter/flutter/issues/181562
bool _isMacMetal() {
return Platform.isMacOS && Platform.executableArguments.contains('--impeller-backend=metal');
}
// Expect that all of the shaders in this folder render green.
// Keeping the outer loop of the test synchronous allows for easy printing
// of the file name within the test case.