| Flutter Impeller |

runtime_stage_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <future>
6
7 #include "flutter/fml/make_copyable.h"
8 #include "flutter/testing/testing.h"
9 #include "gmock/gmock.h"
10 #include "gtest/gtest.h"
11 #include "impeller/base/allocation.h"
12 #include "impeller/base/validation.h"
13 #include "impeller/core/runtime_types.h"
14 #include "impeller/core/shader_types.h"
15 #include "impeller/entity/runtime_effect.vert.h"
16 #include "impeller/playground/playground.h"
17 #include "impeller/renderer/pipeline_descriptor.h"
18 #include "impeller/renderer/pipeline_library.h"
19 #include "impeller/renderer/shader_library.h"
20 #include "impeller/runtime_stage/runtime_stage.h"
21 #include "impeller/runtime_stage/runtime_stage_flatbuffers.h"
22 #include "impeller/runtime_stage/runtime_stage_playground.h"
23 #include "runtime_stage_types_flatbuffers.h"
24 #include "third_party/abseil-cpp/absl/status/status_matchers.h"
25
26 namespace impeller {
27 namespace testing {
28
29 using RuntimeStageTest = RuntimeStagePlayground;
30 INSTANTIATE_PLAYGROUND_SUITE(RuntimeStageTest);
31
32 TEST_P(RuntimeStageTest, CanReadValidBlob) {
33 const std::shared_ptr<fml::Mapping> fixture =
34 flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
35 ASSERT_TRUE(fixture);
36 ASSERT_GT(fixture->GetSize(), 0u);
37 auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
38 ABSL_ASSERT_OK(stages);
39 auto stage =
40 stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
41 ASSERT_TRUE(stage);
42 ASSERT_EQ(stage->GetShaderStage(), RuntimeShaderStage::kFragment);
43 }
44
45 TEST_P(RuntimeStageTest, RejectInvalidFormatVersion) {
46 flatbuffers::FlatBufferBuilder builder;
47 fb::RuntimeStagesBuilder stages_builder(builder);
48 stages_builder.add_format_version(0);
49 auto stages = stages_builder.Finish();
50 builder.Finish(stages, fb::RuntimeStagesIdentifier());
51 auto mapping = std::make_shared<fml::NonOwnedMapping>(
52 builder.GetBufferPointer(), builder.GetSize());
53 auto runtime_stages = RuntimeStage::DecodeRuntimeStages(mapping);
54 EXPECT_FALSE(runtime_stages.ok());
55 EXPECT_EQ(runtime_stages.status().code(), absl::StatusCode::kInvalidArgument);
56 }
57
58 TEST_P(RuntimeStageTest, CanRejectInvalidBlob) {
59 ScopedValidationDisable disable_validation;
60 const std::shared_ptr<fml::Mapping> fixture =
61 flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
62 ASSERT_TRUE(fixture);
63 auto junk_allocation = std::make_shared<Allocation>();
64 ASSERT_TRUE(junk_allocation->Truncate(Bytes{fixture->GetSize()}, false));
65 // Not meant to be secure. Just reject obviously bad blobs using magic
66 // numbers.
67 ::memset(junk_allocation->GetBuffer(), 127,
68 junk_allocation->GetLength().GetByteSize());
69 auto stages = RuntimeStage::DecodeRuntimeStages(
70 CreateMappingFromAllocation(junk_allocation));
71 ASSERT_FALSE(stages.ok());
72 }
73
74 TEST_P(RuntimeStageTest, CanReadUniforms) {
75 const std::shared_ptr<fml::Mapping> fixture =
76 flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
77 ASSERT_TRUE(fixture);
78 ASSERT_GT(fixture->GetSize(), 0u);
79 auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
80 ABSL_ASSERT_OK(stages);
81 auto stage =
82 stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
83
84 ASSERT_TRUE(stage);
85 switch (GetBackend()) {
86 case PlaygroundBackend::kMetal:
87 [[fallthrough]];
88 case PlaygroundBackend::kOpenGLES: {
89 ASSERT_EQ(stage->GetUniforms().size(), 17u);
90 {
91 auto uni = stage->GetUniform("u_color");
92 ASSERT_NE(uni, nullptr);
93 EXPECT_EQ(uni->dimensions.rows, 4u);
94 EXPECT_EQ(uni->dimensions.cols, 1u);
95 EXPECT_EQ(uni->location, 0u);
96 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
97 }
98 {
99 auto uni = stage->GetUniform("u_alpha");
100 ASSERT_NE(uni, nullptr);
101 EXPECT_EQ(uni->dimensions.rows, 1u);
102 EXPECT_EQ(uni->dimensions.cols, 1u);
103 EXPECT_EQ(uni->location, 1u);
104 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
105 }
106 {
107 auto uni = stage->GetUniform("u_sparkle_color");
108 ASSERT_NE(uni, nullptr);
109 EXPECT_EQ(uni->dimensions.rows, 4u);
110 EXPECT_EQ(uni->dimensions.cols, 1u);
111 EXPECT_EQ(uni->location, 2u);
112 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
113 }
114 {
115 auto uni = stage->GetUniform("u_sparkle_alpha");
116 ASSERT_NE(uni, nullptr);
117 EXPECT_EQ(uni->dimensions.rows, 1u);
118 EXPECT_EQ(uni->dimensions.cols, 1u);
119 EXPECT_EQ(uni->location, 3u);
120 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
121 }
122 {
123 auto uni = stage->GetUniform("u_blur");
124 ASSERT_NE(uni, nullptr);
125 EXPECT_EQ(uni->dimensions.rows, 1u);
126 EXPECT_EQ(uni->dimensions.cols, 1u);
127 EXPECT_EQ(uni->location, 4u);
128 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
129 }
130 {
131 auto uni = stage->GetUniform("u_radius_scale");
132 ASSERT_NE(uni, nullptr);
133 EXPECT_EQ(uni->dimensions.rows, 1u);
134 EXPECT_EQ(uni->dimensions.cols, 1u);
135 EXPECT_EQ(uni->location, 6u);
136 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
137 }
138 {
139 auto uni = stage->GetUniform("u_max_radius");
140 ASSERT_NE(uni, nullptr);
141 EXPECT_EQ(uni->dimensions.rows, 1u);
142 EXPECT_EQ(uni->dimensions.cols, 1u);
143 EXPECT_EQ(uni->location, 7u);
144 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
145 }
146 {
147 auto uni = stage->GetUniform("u_resolution_scale");
148 ASSERT_NE(uni, nullptr);
149 EXPECT_EQ(uni->dimensions.rows, 2u);
150 EXPECT_EQ(uni->dimensions.cols, 1u);
151 EXPECT_EQ(uni->location, 8u);
152 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
153 }
154 {
155 auto uni = stage->GetUniform("u_noise_scale");
156 ASSERT_NE(uni, nullptr);
157 EXPECT_EQ(uni->dimensions.rows, 2u);
158 EXPECT_EQ(uni->dimensions.cols, 1u);
159 EXPECT_EQ(uni->location, 9u);
160 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
161 }
162 {
163 auto uni = stage->GetUniform("u_noise_phase");
164 ASSERT_NE(uni, nullptr);
165 EXPECT_EQ(uni->dimensions.rows, 1u);
166 EXPECT_EQ(uni->dimensions.cols, 1u);
167 EXPECT_EQ(uni->location, 10u);
168 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
169 }
170
171 {
172 auto uni = stage->GetUniform("u_circle1");
173 ASSERT_NE(uni, nullptr);
174 EXPECT_EQ(uni->dimensions.rows, 2u);
175 EXPECT_EQ(uni->dimensions.cols, 1u);
176 EXPECT_EQ(uni->location, 11u);
177 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
178 }
179 {
180 auto uni = stage->GetUniform("u_circle2");
181 ASSERT_NE(uni, nullptr);
182 EXPECT_EQ(uni->dimensions.rows, 2u);
183 EXPECT_EQ(uni->dimensions.cols, 1u);
184 EXPECT_EQ(uni->location, 12u);
185 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
186 }
187 {
188 auto uni = stage->GetUniform("u_circle3");
189 ASSERT_NE(uni, nullptr);
190 EXPECT_EQ(uni->dimensions.rows, 2u);
191 EXPECT_EQ(uni->dimensions.cols, 1u);
192 EXPECT_EQ(uni->location, 13u);
193 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
194 }
195 {
196 auto uni = stage->GetUniform("u_rotation1");
197 ASSERT_NE(uni, nullptr);
198 EXPECT_EQ(uni->dimensions.rows, 2u);
199 EXPECT_EQ(uni->dimensions.cols, 1u);
200 EXPECT_EQ(uni->location, 14u);
201 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
202 }
203 {
204 auto uni = stage->GetUniform("u_rotation2");
205 ASSERT_NE(uni, nullptr);
206 EXPECT_EQ(uni->dimensions.rows, 2u);
207 EXPECT_EQ(uni->dimensions.cols, 1u);
208 EXPECT_EQ(uni->location, 15u);
209 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
210 }
211 {
212 auto uni = stage->GetUniform("u_rotation3");
213 ASSERT_NE(uni, nullptr);
214 EXPECT_EQ(uni->dimensions.rows, 2u);
215 EXPECT_EQ(uni->dimensions.cols, 1u);
216 EXPECT_EQ(uni->location, 16u);
217 EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
218 }
219 break;
220 }
221 case PlaygroundBackend::kVulkan: {
222 EXPECT_EQ(stage->GetUniforms().size(), 1u);
223 auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
224 ASSERT_TRUE(uni);
225 EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
226 EXPECT_EQ(uni->struct_float_count, 32u);
227
228 // There are 36 4 byte chunks in the UBO: 32 for the 32 floats, and 4 for
229 // padding. Initialize a vector as if they'll all be floats, then manually
230 // set the few padding bytes. If the shader changes, the padding locations
231 // will change as well. For example, if `u_alpha` was moved to the end,
232 // three bytes of padding could potentially be dropped - or if some of the
233 // scalar floats were changed to vec2 or vec4s, or if any vec3s are
234 // introduced.
235 // This means 36 * 4 = 144 bytes total.
236
237 EXPECT_EQ(uni->GetSize(), 144u);
238 std::vector<uint8_t> layout(uni->GetSize() / sizeof(float), 1);
239 layout[5] = 0;
240 layout[6] = 0;
241 layout[7] = 0;
242 layout[23] = 0;
243
244 EXPECT_THAT(uni->struct_layout, ::testing::ElementsAreArray(layout));
245 break;
246 }
247 }
248 }
249
250 TEST_P(RuntimeStageTest, CanReadUniformsSamplerBeforeUBO) {
251 if (GetBackend() != PlaygroundBackend::kVulkan) {
252 GTEST_SKIP() << "Test only relevant for Vulkan";
253 }
254 const std::shared_ptr<fml::Mapping> fixture =
255 flutter::testing::OpenFixtureAsMapping(
256 "uniforms_and_sampler_1.frag.iplr");
257 ASSERT_TRUE(fixture);
258 ASSERT_GT(fixture->GetSize(), 0u);
259 auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
260 ABSL_ASSERT_OK(stages);
261 auto stage =
262 stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
263
264 EXPECT_EQ(stage->GetUniforms().size(), 2u);
265 auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
266 ASSERT_TRUE(uni);
267 // Struct must be offset at 65.
268 EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
269 EXPECT_EQ(uni->binding, 65u);
270 // Sampler should be offset at 64 but due to current bug
271 // has offset of 0, the correct offset is computed at runtime.
272 auto sampler_uniform = stage->GetUniform("u_texture");
273 EXPECT_EQ(sampler_uniform->type, RuntimeUniformType::kSampledImage);
274 EXPECT_EQ(sampler_uniform->binding, 64u);
275 }
276
277 TEST_P(RuntimeStageTest, CanReadUniformsSamplerAfterUBO) {
278 if (GetBackend() != PlaygroundBackend::kVulkan) {
279 GTEST_SKIP() << "Test only relevant for Vulkan";
280 }
281 const std::shared_ptr<fml::Mapping> fixture =
282 flutter::testing::OpenFixtureAsMapping(
283 "uniforms_and_sampler_2.frag.iplr");
284 ASSERT_TRUE(fixture);
285 ASSERT_GT(fixture->GetSize(), 0u);
286 auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
287 ABSL_ASSERT_OK(stages);
288 auto stage =
289 stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
290
291 EXPECT_EQ(stage->GetUniforms().size(), 2u);
292 auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
293 ASSERT_TRUE(uni);
294 // Struct must be offset at 45.
295 EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
296 EXPECT_EQ(uni->binding, 64u);
297 // Sampler should be offset at 64 but due to current bug
298 // has offset of 0, the correct offset is computed at runtime.
299 auto sampler_uniform = stage->GetUniform("u_texture");
300 EXPECT_EQ(sampler_uniform->type, RuntimeUniformType::kSampledImage);
301 EXPECT_EQ(sampler_uniform->binding, 65u);
302 }
303
304 TEST_P(RuntimeStageTest, CanRegisterStage) {
305 const std::shared_ptr<fml::Mapping> fixture =
306 flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
307 ASSERT_TRUE(fixture);
308 ASSERT_GT(fixture->GetSize(), 0u);
309 auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
310 ABSL_ASSERT_OK(stages);
311 auto stage =
312 stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
313 ASSERT_TRUE(stage);
314 std::promise<bool> registration;
315 auto future = registration.get_future();
316 auto library = GetContext()->GetShaderLibrary();
317 library->RegisterFunction(
318 stage->GetEntrypoint(), //
319 ToShaderStage(stage->GetShaderStage()), //
320 stage->GetCodeMapping(), //
321 fml::MakeCopyable([reg = std::move(registration)](bool result) mutable {
322 reg.set_value(result);
323 }));
324 ASSERT_TRUE(future.get());
325 {
326 auto function =
327 library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
328 ASSERT_NE(function, nullptr);
329 }
330
331 // Check if unregistering works.
332
333 library->UnregisterFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
334 {
335 auto function =
336 library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
337 ASSERT_EQ(function, nullptr);
338 }
339 }
340
341 TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) {
342 auto stages_result = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr");
343 ABSL_ASSERT_OK(stages_result);
344 auto stage =
345 stages_result
346 .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
347
348 ASSERT_TRUE(stage);
349 ASSERT_NE(stage, nullptr);
350 ASSERT_TRUE(RegisterStage(*stage));
351 auto library = GetContext()->GetShaderLibrary();
352 using VS = RuntimeEffectVertexShader;
353 PipelineDescriptor desc;
354 desc.SetLabel("Runtime Stage InkSparkle");
355 desc.AddStageEntrypoint(
356 library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex));
357 desc.AddStageEntrypoint(
358 library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment));
359 auto vertex_descriptor = std::make_shared<VertexDescriptor>();
360 vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs,
361 VS::kInterleavedBufferLayout);
362
363 std::array<DescriptorSetLayout, 2> descriptor_set_layouts = {
364 VS::kDescriptorSetLayouts[0],
365 DescriptorSetLayout{
366 .binding = 64u,
367 .descriptor_type = DescriptorType::kUniformBuffer,
368 .shader_stage = ShaderStage::kFragment,
369 },
370 };
371 vertex_descriptor->RegisterDescriptorSetLayouts(descriptor_set_layouts);
372
373 desc.SetVertexDescriptor(std::move(vertex_descriptor));
374 ColorAttachmentDescriptor color0;
375 color0.format = GetContext()->GetCapabilities()->GetDefaultColorFormat();
376 StencilAttachmentDescriptor stencil0;
377 stencil0.stencil_compare = CompareFunction::kEqual;
378 desc.SetColorAttachmentDescriptor(0u, color0);
379 desc.SetStencilAttachmentDescriptors(stencil0);
380 const auto stencil_fmt =
381 GetContext()->GetCapabilities()->GetDefaultStencilFormat();
382 desc.SetStencilPixelFormat(stencil_fmt);
383 auto pipeline = GetContext()->GetPipelineLibrary()->GetPipeline(desc).Get();
384 ASSERT_NE(pipeline, nullptr);
385 }
386
387 TEST_P(RuntimeStageTest, ContainsExpectedShaderTypes) {
388 auto stages_result = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr");
389 ABSL_ASSERT_OK(stages_result);
390 auto stages = stages_result.value();
391 // Right now, SkSL gets implicitly bundled regardless of what the build rule
392 // for this test requested. After
393 // https://github.com/flutter/flutter/issues/138919, this may require a build
394 // rule change or a new test.
395 EXPECT_TRUE(stages[RuntimeStageBackend::kSkSL]);
396
397 EXPECT_TRUE(stages[RuntimeStageBackend::kOpenGLES]);
398 EXPECT_TRUE(stages[RuntimeStageBackend::kMetal]);
399 EXPECT_TRUE(stages[RuntimeStageBackend::kVulkan]);
400 }
401
402 } // namespace testing
403 } // namespace impeller
allocation.h
impeller::AllocationSize< 1u >
impeller::PipelineDescriptor
Definition: pipeline_descriptor.h:24
impeller::PipelineDescriptor::SetStencilPixelFormat
PipelineDescriptor & SetStencilPixelFormat(PixelFormat format)
Definition: pipeline_descriptor.cc:146
impeller::PipelineDescriptor::SetVertexDescriptor
PipelineDescriptor & SetVertexDescriptor(std::shared_ptr< VertexDescriptor > vertex_descriptor)
Definition: pipeline_descriptor.cc:97
impeller::PipelineDescriptor::AddStageEntrypoint
PipelineDescriptor & AddStageEntrypoint(std::shared_ptr< const ShaderFunction > function)
Definition: pipeline_descriptor.cc:82
impeller::PipelineDescriptor::SetLabel
PipelineDescriptor & SetLabel(std::string_view label)
Definition: pipeline_descriptor.cc:72
impeller::PipelineDescriptor::SetStencilAttachmentDescriptors
PipelineDescriptor & SetStencilAttachmentDescriptors(std::optional< StencilAttachmentDescriptor > front_and_back)
Definition: pipeline_descriptor.cc:158
impeller::PipelineDescriptor::SetColorAttachmentDescriptor
PipelineDescriptor & SetColorAttachmentDescriptor(size_t index, ColorAttachmentDescriptor desc)
Definition: pipeline_descriptor.cc:111
impeller::RuntimeStage::kVulkanUBOName
static const char * kVulkanUBOName
Definition: runtime_stage.h:23
impeller::RuntimeStage::DecodeRuntimeStages
static absl::StatusOr< Map > DecodeRuntimeStages(const std::shared_ptr< fml::Mapping > &payload)
Definition: runtime_stage.cc:143
impeller::RuntimeStagePlayground
Definition: runtime_stage_playground.h:13
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:59
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller
Definition: allocation.cc:12
impeller::RuntimeShaderStage::kFragment
@ kFragment
impeller::RuntimeStageBackend::kOpenGLES
@ kOpenGLES
impeller::RuntimeStageBackend::kMetal
@ kMetal
impeller::RuntimeStageBackend::kVulkan
@ kVulkan
impeller::RuntimeStageBackend::kSkSL
@ kSkSL
impeller::PlaygroundBackendToRuntimeStageBackend
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition: playground.h:33
impeller::ToShaderStage
constexpr ShaderStage ToShaderStage(RuntimeShaderStage stage)
Definition: shader_types.h:29
impeller::CompareFunction::kEqual
@ kEqual
Comparison test passes if new_value == current_value.
impeller::ShaderStage::kFragment
@ kFragment
impeller::ShaderStage::kVertex
@ kVertex
impeller::CreateMappingFromAllocation
std::shared_ptr< fml::Mapping > CreateMappingFromAllocation(const std::shared_ptr< Allocation > &allocation)
Creates a mapping from allocation.
Definition: allocation.cc:99
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::PlaygroundBackend::kMetal
@ kMetal
impeller::PlaygroundBackend::kVulkan
@ kVulkan
impeller::VS
LinePipeline::VertexShader VS
Definition: line_contents.cc:13
impeller::DescriptorType::kUniformBuffer
@ kUniformBuffer
impeller::kSampledImage
@ kSampledImage
Definition: runtime_types.h:26
impeller::kFloat
@ kFloat
Definition: runtime_types.h:25
impeller::kStruct
@ kStruct
Definition: runtime_types.h:27
pipeline_descriptor.h
pipeline_library.h
playground.h
runtime_stage.h
runtime_stage_playground.h
runtime_types.h
shader_library.h
shader_types.h
impeller::ColorAttachmentDescriptor
Describe the color attachment that will be used with this pipeline.
Definition: formats.h:522
impeller::ColorAttachmentDescriptor::format
PixelFormat format
Definition: formats.h:523
impeller::DescriptorSetLayout
Definition: shader_types.h:167
impeller::DescriptorSetLayout::binding
uint32_t binding
Definition: shader_types.h:168
impeller::ScopedValidationDisable
Definition: validation.h:56
impeller::StencilAttachmentDescriptor
Definition: formats.h:614
impeller::StencilAttachmentDescriptor::stencil_compare
CompareFunction stencil_compare
Definition: formats.h:620
validation.h