1 //
2 // Copyright 2016 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // renderer_utils:
7 // Helper methods pertaining to most or all back-ends.
8 //
9
10 #ifndef LIBANGLE_RENDERER_RENDERER_UTILS_H_
11 #define LIBANGLE_RENDERER_RENDERER_UTILS_H_
12
13 #include <cstdint>
14
15 #include <limits>
16 #include <map>
17
18 #include "GLSLANG/ShaderLang.h"
19 #include "common/angleutils.h"
20 #include "common/utilities.h"
21 #include "libANGLE/ImageIndex.h"
22 #include "libANGLE/angletypes.h"
23
24 namespace angle
25 {
26 struct FeatureSetBase;
27 struct Format;
28 struct ImageLoadContext;
29 enum class FormatID;
30 } // namespace angle
31
32 namespace gl
33 {
34 struct FormatType;
35 struct InternalFormat;
36 class ProgramExecutable;
37 class State;
38 } // namespace gl
39
40 namespace egl
41 {
42 class AttributeMap;
43 struct DisplayState;
44 } // namespace egl
45
46 namespace sh
47 {
48 struct BlockMemberInfo;
49 }
50
51 namespace rx
52 {
53 class ContextImpl;
54
55 // The possible rotations of the surface/draw framebuffer, particularly for the Vulkan back-end on
56 // Android.
57 enum class SurfaceRotation
58 {
59 Identity,
60 Rotated90Degrees,
61 Rotated180Degrees,
62 Rotated270Degrees,
63 FlippedIdentity,
64 FlippedRotated90Degrees,
65 FlippedRotated180Degrees,
66 FlippedRotated270Degrees,
67
68 InvalidEnum,
69 EnumCount = InvalidEnum,
70 };
71
72 bool IsRotatedAspectRatio(SurfaceRotation rotation);
73
74 using SpecConstUsageBits = angle::PackedEnumBitSet<sh::vk::SpecConstUsage, uint32_t>;
75
76 void RotateRectangle(const SurfaceRotation rotation,
77 const bool flipY,
78 const int framebufferWidth,
79 const int framebufferHeight,
80 const gl::Rectangle &incoming,
81 gl::Rectangle *outgoing);
82
83 using MipGenerationFunction = void (*)(size_t sourceWidth,
84 size_t sourceHeight,
85 size_t sourceDepth,
86 const uint8_t *sourceData,
87 size_t sourceRowPitch,
88 size_t sourceDepthPitch,
89 uint8_t *destData,
90 size_t destRowPitch,
91 size_t destDepthPitch);
92
93 typedef void (*PixelReadFunction)(const uint8_t *source, uint8_t *dest);
94 typedef void (*PixelWriteFunction)(const uint8_t *source, uint8_t *dest);
95 typedef void (*FastCopyFunction)(const uint8_t *source,
96 int srcXAxisPitch,
97 int srcYAxisPitch,
98 uint8_t *dest,
99 int destXAxisPitch,
100 int destYAxisPitch,
101 int width,
102 int height);
103
104 class FastCopyFunctionMap
105 {
106 public:
107 struct Entry
108 {
109 angle::FormatID formatID;
110 FastCopyFunction func;
111 };
112
FastCopyFunctionMap()113 constexpr FastCopyFunctionMap() : FastCopyFunctionMap(nullptr, 0) {}
114
FastCopyFunctionMap(const Entry * data,size_t size)115 constexpr FastCopyFunctionMap(const Entry *data, size_t size) : mSize(size), mData(data) {}
116
117 bool has(angle::FormatID formatID) const;
118 FastCopyFunction get(angle::FormatID formatID) const;
119
120 private:
121 size_t mSize;
122 const Entry *mData;
123 };
124
125 struct PackPixelsParams
126 {
127 PackPixelsParams();
128 PackPixelsParams(const gl::Rectangle &area,
129 const angle::Format &destFormat,
130 GLuint outputPitch,
131 bool reverseRowOrderIn,
132 gl::Buffer *packBufferIn,
133 ptrdiff_t offset);
134
135 gl::Rectangle area;
136 const angle::Format *destFormat;
137 GLuint outputPitch;
138 gl::Buffer *packBuffer;
139 bool reverseRowOrder;
140 ptrdiff_t offset;
141 SurfaceRotation rotation;
142 };
143
144 void PackPixels(const PackPixelsParams ¶ms,
145 const angle::Format &sourceFormat,
146 int inputPitch,
147 const uint8_t *source,
148 uint8_t *destination);
149
150 angle::Result GetPackPixelsParams(const gl::InternalFormat &sizedFormatInfo,
151 GLuint outputPitch,
152 const gl::PixelPackState &packState,
153 gl::Buffer *packBuffer,
154 const gl::Rectangle &area,
155 const gl::Rectangle &clippedArea,
156 rx::PackPixelsParams *paramsOut,
157 GLuint *skipBytesOut);
158
159 using InitializeTextureDataFunction = void (*)(size_t width,
160 size_t height,
161 size_t depth,
162 uint8_t *output,
163 size_t outputRowPitch,
164 size_t outputDepthPitch);
165
166 using LoadImageFunction = void (*)(const angle::ImageLoadContext &context,
167 size_t width,
168 size_t height,
169 size_t depth,
170 const uint8_t *input,
171 size_t inputRowPitch,
172 size_t inputDepthPitch,
173 uint8_t *output,
174 size_t outputRowPitch,
175 size_t outputDepthPitch);
176
177 struct LoadImageFunctionInfo
178 {
LoadImageFunctionInfoLoadImageFunctionInfo179 LoadImageFunctionInfo() : loadFunction(nullptr), requiresConversion(false) {}
LoadImageFunctionInfoLoadImageFunctionInfo180 LoadImageFunctionInfo(LoadImageFunction loadFunction, bool requiresConversion)
181 : loadFunction(loadFunction), requiresConversion(requiresConversion)
182 {}
183
184 LoadImageFunction loadFunction;
185 bool requiresConversion;
186 };
187
188 using LoadFunctionMap = LoadImageFunctionInfo (*)(GLenum);
189
190 bool ShouldUseDebugLayers(const egl::AttributeMap &attribs);
191
192 void CopyImageCHROMIUM(const uint8_t *sourceData,
193 size_t sourceRowPitch,
194 size_t sourcePixelBytes,
195 size_t sourceDepthPitch,
196 PixelReadFunction pixelReadFunction,
197 uint8_t *destData,
198 size_t destRowPitch,
199 size_t destPixelBytes,
200 size_t destDepthPitch,
201 PixelWriteFunction pixelWriteFunction,
202 GLenum destUnsizedFormat,
203 GLenum destComponentType,
204 size_t width,
205 size_t height,
206 size_t depth,
207 bool unpackFlipY,
208 bool unpackPremultiplyAlpha,
209 bool unpackUnmultiplyAlpha);
210
211 // Incomplete textures are 1x1 textures filled with black, used when samplers are incomplete.
212 // This helper class encapsulates handling incomplete textures. Because the GL back-end
213 // can take advantage of the driver's incomplete textures, and because clearing multisample
214 // textures is so difficult, we can keep an instance of this class in the back-end instead
215 // of moving the logic to the Context front-end.
216
217 // This interface allows us to call-back to init a multisample texture.
218 class MultisampleTextureInitializer
219 {
220 public:
~MultisampleTextureInitializer()221 virtual ~MultisampleTextureInitializer() {}
222 virtual angle::Result initializeMultisampleTextureToBlack(const gl::Context *context,
223 gl::Texture *glTexture) = 0;
224 };
225
226 class IncompleteTextureSet final : angle::NonCopyable
227 {
228 public:
229 IncompleteTextureSet();
230 ~IncompleteTextureSet();
231
232 void onDestroy(const gl::Context *context);
233
234 angle::Result getIncompleteTexture(const gl::Context *context,
235 gl::TextureType type,
236 gl::SamplerFormat format,
237 MultisampleTextureInitializer *multisampleInitializer,
238 gl::Texture **textureOut);
239
240 private:
241 using TextureMapWithSamplerFormat = angle::PackedEnumMap<gl::SamplerFormat, gl::TextureMap>;
242
243 TextureMapWithSamplerFormat mIncompleteTextures;
244 };
245
246 // Helpers to set a matrix uniform value based on GLSL or HLSL semantics.
247 // The return value indicate if the data was updated or not.
248 template <int cols, int rows>
249 struct SetFloatUniformMatrixGLSL
250 {
251 static void Run(unsigned int arrayElementOffset,
252 unsigned int elementCount,
253 GLsizei countIn,
254 GLboolean transpose,
255 const GLfloat *value,
256 uint8_t *targetData);
257 };
258
259 template <int cols, int rows>
260 struct SetFloatUniformMatrixHLSL
261 {
262 static void Run(unsigned int arrayElementOffset,
263 unsigned int elementCount,
264 GLsizei countIn,
265 GLboolean transpose,
266 const GLfloat *value,
267 uint8_t *targetData);
268 };
269
270 // Helper method to de-tranpose a matrix uniform for an API query.
271 void GetMatrixUniform(GLenum type, GLfloat *dataOut, const GLfloat *source, bool transpose);
272
273 template <typename NonFloatT>
274 void GetMatrixUniform(GLenum type, NonFloatT *dataOut, const NonFloatT *source, bool transpose);
275
276 // Contains a CPU-side buffer and its data layout, used as a shadow buffer for default uniform
277 // blocks in VK and WGPU backends.
278 struct BufferAndLayout final : private angle::NonCopyable
279 {
280 BufferAndLayout();
281 ~BufferAndLayout();
282
283 // Shadow copies of the shader uniform data.
284 angle::MemoryBuffer uniformData;
285
286 // Tells us where to write on a call to a setUniform method. They are arranged in uniform
287 // location order.
288 std::vector<sh::BlockMemberInfo> uniformLayout;
289 };
290
291 template <typename T>
292 void UpdateBufferWithLayout(GLsizei count,
293 uint32_t arrayIndex,
294 int componentCount,
295 const T *v,
296 const sh::BlockMemberInfo &layoutInfo,
297 angle::MemoryBuffer *uniformData);
298
299 template <typename T>
300 void ReadFromBufferWithLayout(int componentCount,
301 uint32_t arrayIndex,
302 T *dst,
303 const sh::BlockMemberInfo &layoutInfo,
304 const angle::MemoryBuffer *uniformData);
305
306 using DefaultUniformBlockMap = gl::ShaderMap<std::shared_ptr<BufferAndLayout>>;
307
308 template <typename T>
309 void SetUniform(const gl::ProgramExecutable *executable,
310 GLint location,
311 GLsizei count,
312 const T *v,
313 GLenum entryPointType,
314 DefaultUniformBlockMap *defaultUniformBlocks,
315 gl::ShaderBitSet *defaultUniformBlocksDirty);
316
317 template <int cols, int rows>
318 void SetUniformMatrixfv(const gl::ProgramExecutable *executable,
319 GLint location,
320 GLsizei count,
321 GLboolean transpose,
322 const GLfloat *value,
323 DefaultUniformBlockMap *defaultUniformBlocks,
324 gl::ShaderBitSet *defaultUniformBlocksDirty);
325
326 template <typename T>
327 void GetUniform(const gl::ProgramExecutable *executable,
328 GLint location,
329 T *v,
330 GLenum entryPointType,
331 const DefaultUniformBlockMap *defaultUniformBlocks);
332
333 const angle::Format &GetFormatFromFormatType(GLenum format, GLenum type);
334
335 angle::Result ComputeStartVertex(ContextImpl *contextImpl,
336 const gl::IndexRange &indexRange,
337 GLint baseVertex,
338 GLint *firstVertexOut);
339
340 angle::Result GetVertexRangeInfo(const gl::Context *context,
341 GLint firstVertex,
342 GLsizei vertexOrIndexCount,
343 gl::DrawElementsType indexTypeOrInvalid,
344 const void *indices,
345 GLint baseVertex,
346 GLint *startVertexOut,
347 size_t *vertexCountOut);
348
349 gl::Rectangle ClipRectToScissor(const gl::State &glState, const gl::Rectangle &rect, bool invertY);
350
351 // Helper method to intialize a FeatureSet with overrides from the DisplayState
352 void ApplyFeatureOverrides(angle::FeatureSetBase *features,
353 const angle::FeatureOverrides &overrides);
354
355 template <typename In>
LineLoopRestartIndexCountHelper(GLsizei indexCount,const uint8_t * srcPtr)356 uint32_t LineLoopRestartIndexCountHelper(GLsizei indexCount, const uint8_t *srcPtr)
357 {
358 constexpr In restartIndex = gl::GetPrimitiveRestartIndexFromType<In>();
359 const In *inIndices = reinterpret_cast<const In *>(srcPtr);
360 uint32_t numIndices = 0;
361 // See CopyLineLoopIndicesWithRestart() below for more info on how
362 // numIndices is calculated.
363 GLsizei loopStartIndex = 0;
364 for (GLsizei curIndex = 0; curIndex < indexCount; curIndex++)
365 {
366 In vertex = inIndices[curIndex];
367 if (vertex != restartIndex)
368 {
369 numIndices++;
370 }
371 else
372 {
373 if (curIndex > loopStartIndex)
374 {
375 numIndices += 2;
376 }
377 loopStartIndex = curIndex + 1;
378 }
379 }
380 if (indexCount > loopStartIndex)
381 {
382 numIndices++;
383 }
384 return numIndices;
385 }
386
GetLineLoopWithRestartIndexCount(gl::DrawElementsType glIndexType,GLsizei indexCount,const uint8_t * srcPtr)387 inline uint32_t GetLineLoopWithRestartIndexCount(gl::DrawElementsType glIndexType,
388 GLsizei indexCount,
389 const uint8_t *srcPtr)
390 {
391 switch (glIndexType)
392 {
393 case gl::DrawElementsType::UnsignedByte:
394 return LineLoopRestartIndexCountHelper<uint8_t>(indexCount, srcPtr);
395 case gl::DrawElementsType::UnsignedShort:
396 return LineLoopRestartIndexCountHelper<uint16_t>(indexCount, srcPtr);
397 case gl::DrawElementsType::UnsignedInt:
398 return LineLoopRestartIndexCountHelper<uint32_t>(indexCount, srcPtr);
399 default:
400 UNREACHABLE();
401 return 0;
402 }
403 }
404
405 // Writes the line-strip vertices for a line loop to outPtr,
406 // where outLimit is calculated as in GetPrimitiveRestartIndexCount.
407 template <typename In, typename Out>
CopyLineLoopIndicesWithRestart(GLsizei indexCount,const uint8_t * srcPtr,uint8_t * outPtr)408 void CopyLineLoopIndicesWithRestart(GLsizei indexCount, const uint8_t *srcPtr, uint8_t *outPtr)
409 {
410 constexpr In restartIndex = gl::GetPrimitiveRestartIndexFromType<In>();
411 constexpr Out outRestartIndex = gl::GetPrimitiveRestartIndexFromType<Out>();
412 const In *inIndices = reinterpret_cast<const In *>(srcPtr);
413 Out *outIndices = reinterpret_cast<Out *>(outPtr);
414 GLsizei loopStartIndex = 0;
415 for (GLsizei curIndex = 0; curIndex < indexCount; curIndex++)
416 {
417 In vertex = inIndices[curIndex];
418 if (vertex != restartIndex)
419 {
420 *(outIndices++) = static_cast<Out>(vertex);
421 }
422 else
423 {
424 if (curIndex > loopStartIndex)
425 {
426 // Emit an extra vertex only if the loop is not empty.
427 *(outIndices++) = inIndices[loopStartIndex];
428 // Then restart the strip.
429 *(outIndices++) = outRestartIndex;
430 }
431 loopStartIndex = curIndex + 1;
432 }
433 }
434 if (indexCount > loopStartIndex)
435 {
436 // Close the last loop if not empty.
437 *(outIndices++) = inIndices[loopStartIndex];
438 }
439 }
440
441 void GetSamplePosition(GLsizei sampleCount, size_t index, GLfloat *xy);
442
443 angle::Result MultiDrawArraysGeneral(ContextImpl *contextImpl,
444 const gl::Context *context,
445 gl::PrimitiveMode mode,
446 const GLint *firsts,
447 const GLsizei *counts,
448 GLsizei drawcount);
449 angle::Result MultiDrawArraysIndirectGeneral(ContextImpl *contextImpl,
450 const gl::Context *context,
451 gl::PrimitiveMode mode,
452 const void *indirect,
453 GLsizei drawcount,
454 GLsizei stride);
455 angle::Result MultiDrawArraysInstancedGeneral(ContextImpl *contextImpl,
456 const gl::Context *context,
457 gl::PrimitiveMode mode,
458 const GLint *firsts,
459 const GLsizei *counts,
460 const GLsizei *instanceCounts,
461 GLsizei drawcount);
462 angle::Result MultiDrawElementsGeneral(ContextImpl *contextImpl,
463 const gl::Context *context,
464 gl::PrimitiveMode mode,
465 const GLsizei *counts,
466 gl::DrawElementsType type,
467 const GLvoid *const *indices,
468 GLsizei drawcount);
469 angle::Result MultiDrawElementsIndirectGeneral(ContextImpl *contextImpl,
470 const gl::Context *context,
471 gl::PrimitiveMode mode,
472 gl::DrawElementsType type,
473 const void *indirect,
474 GLsizei drawcount,
475 GLsizei stride);
476 angle::Result MultiDrawElementsInstancedGeneral(ContextImpl *contextImpl,
477 const gl::Context *context,
478 gl::PrimitiveMode mode,
479 const GLsizei *counts,
480 gl::DrawElementsType type,
481 const GLvoid *const *indices,
482 const GLsizei *instanceCounts,
483 GLsizei drawcount);
484 angle::Result MultiDrawArraysInstancedBaseInstanceGeneral(ContextImpl *contextImpl,
485 const gl::Context *context,
486 gl::PrimitiveMode mode,
487 const GLint *firsts,
488 const GLsizei *counts,
489 const GLsizei *instanceCounts,
490 const GLuint *baseInstances,
491 GLsizei drawcount);
492 angle::Result MultiDrawElementsInstancedBaseVertexBaseInstanceGeneral(ContextImpl *contextImpl,
493 const gl::Context *context,
494 gl::PrimitiveMode mode,
495 const GLsizei *counts,
496 gl::DrawElementsType type,
497 const GLvoid *const *indices,
498 const GLsizei *instanceCounts,
499 const GLint *baseVertices,
500 const GLuint *baseInstances,
501 GLsizei drawcount);
502
503 // RAII object making sure reset uniforms is called no matter whether there's an error in draw calls
504 class ResetBaseVertexBaseInstance : angle::NonCopyable
505 {
506 public:
507 ResetBaseVertexBaseInstance(gl::ProgramExecutable *executable,
508 bool resetBaseVertex,
509 bool resetBaseInstance);
510
511 ~ResetBaseVertexBaseInstance();
512
513 private:
514 gl::ProgramExecutable *mExecutable;
515 bool mResetBaseVertex;
516 bool mResetBaseInstance;
517 };
518
519 angle::FormatID ConvertToSRGB(angle::FormatID formatID);
520 angle::FormatID ConvertToLinear(angle::FormatID formatID);
521 bool IsOverridableLinearFormat(angle::FormatID formatID);
522
523 template <bool swizzledLuma = true>
524 const gl::ColorGeneric AdjustBorderColor(const angle::ColorGeneric &borderColorGeneric,
525 const angle::Format &format,
526 bool stencilMode);
527
528 template <typename LargerInt>
LimitToInt(const LargerInt physicalDeviceValue)529 GLint LimitToInt(const LargerInt physicalDeviceValue)
530 {
531 static_assert(sizeof(LargerInt) >= sizeof(int32_t), "Incorrect usage of LimitToInt");
532 return static_cast<GLint>(
533 std::min(physicalDeviceValue, static_cast<LargerInt>(std::numeric_limits<int32_t>::max())));
534 }
535
536 bool TextureHasAnyRedefinedLevels(const gl::CubeFaceArray<gl::TexLevelMask> &redefinedLevels);
537 bool IsTextureLevelRedefined(const gl::CubeFaceArray<gl::TexLevelMask> &redefinedLevels,
538 gl::TextureType textureType,
539 gl::LevelIndex level);
540
541 enum class TextureLevelDefinition
542 {
543 Compatible = 0,
544 Incompatible = 1,
545
546 InvalidEnum = 2
547 };
548
549 enum class TextureLevelAllocation
550 {
551 WithinAllocatedImage = 0,
552 OutsideAllocatedImage = 1,
553
554 InvalidEnum = 2
555 };
556 // Returns true if the image should be released after the level is redefined, false otherwise.
557 bool TextureRedefineLevel(const TextureLevelAllocation levelAllocation,
558 const TextureLevelDefinition levelDefinition,
559 bool immutableFormat,
560 uint32_t levelCount,
561 const uint32_t layerIndex,
562 const gl::ImageIndex &index,
563 gl::LevelIndex imageFirstAllocatedLevel,
564 gl::CubeFaceArray<gl::TexLevelMask> *redefinedLevels);
565
566 void TextureRedefineGenerateMipmapLevels(gl::LevelIndex baseLevel,
567 gl::LevelIndex maxLevel,
568 gl::LevelIndex firstGeneratedLevel,
569 gl::CubeFaceArray<gl::TexLevelMask> *redefinedLevels);
570
571 enum class ImageMipLevels
572 {
573 EnabledLevels = 0,
574 FullMipChainForGenerateMipmap = 1,
575
576 InvalidEnum = 2,
577 };
578
579 enum class PipelineType
580 {
581 Graphics = 0,
582 Compute = 1,
583
584 InvalidEnum = 2,
585 EnumCount = 2,
586 };
587
588 // Return the log of samples. Assumes |sampleCount| is a power of 2. The result can be used to
589 // index an array based on sample count.
PackSampleCount(int32_t sampleCount)590 inline size_t PackSampleCount(int32_t sampleCount)
591 {
592 if (sampleCount == 0)
593 {
594 sampleCount = 1;
595 }
596
597 // We currently only support up to 16xMSAA.
598 ASSERT(1 <= sampleCount && sampleCount <= 16);
599 ASSERT(gl::isPow2(sampleCount));
600 return gl::ScanForward(static_cast<uint32_t>(sampleCount));
601 }
602
603 } // namespace rx
604
605 // MultiDraw macro patterns
606 // These macros are to avoid too much code duplication as we don't want to have if detect for
607 // hasDrawID/BaseVertex/BaseInstance inside for loop in a multiDrawANGLE call Part of these are put
608 // in the header as we want to share with specialized context impl on some platforms for multidraw
609 #define ANGLE_SET_DRAW_ID_UNIFORM_0(drawID) \
610 {}
611 #define ANGLE_SET_DRAW_ID_UNIFORM_1(drawID) executable->setDrawIDUniform(drawID)
612 #define ANGLE_SET_DRAW_ID_UNIFORM(cond) ANGLE_SET_DRAW_ID_UNIFORM_##cond
613
614 #define ANGLE_SET_BASE_VERTEX_UNIFORM_0(baseVertex) \
615 {}
616 #define ANGLE_SET_BASE_VERTEX_UNIFORM_1(baseVertex) executable->setBaseVertexUniform(baseVertex);
617 #define ANGLE_SET_BASE_VERTEX_UNIFORM(cond) ANGLE_SET_BASE_VERTEX_UNIFORM_##cond
618
619 #define ANGLE_SET_BASE_INSTANCE_UNIFORM_0(baseInstance) \
620 {}
621 #define ANGLE_SET_BASE_INSTANCE_UNIFORM_1(baseInstance) \
622 executable->setBaseInstanceUniform(baseInstance)
623 #define ANGLE_SET_BASE_INSTANCE_UNIFORM(cond) ANGLE_SET_BASE_INSTANCE_UNIFORM_##cond
624
625 #define ANGLE_NOOP_DRAW_ context->noopDraw(mode, counts[drawID])
626 #define ANGLE_NOOP_DRAW_INSTANCED \
627 context->noopDrawInstanced(mode, counts[drawID], instanceCounts[drawID])
628 #define ANGLE_NOOP_DRAW(_instanced) ANGLE_NOOP_DRAW##_instanced
629
630 #define ANGLE_MARK_TRANSFORM_FEEDBACK_USAGE_ \
631 gl::MarkTransformFeedbackBufferUsage(context, counts[drawID], 1)
632 #define ANGLE_MARK_TRANSFORM_FEEDBACK_USAGE_INSTANCED \
633 gl::MarkTransformFeedbackBufferUsage(context, counts[drawID], instanceCounts[drawID])
634 #define ANGLE_MARK_TRANSFORM_FEEDBACK_USAGE(instanced) \
635 ANGLE_MARK_TRANSFORM_FEEDBACK_USAGE##instanced
636
637 #endif // LIBANGLE_RENDERER_RENDERER_UTILS_H_
638