| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <impeller/transform.glsl> |
| #include <impeller/types.glsl> |
| |
| uniform FrameInfo { |
| mat4 mvp; |
| mat4 entity_transform; |
| vec2 atlas_size; |
| vec2 offset; |
| f16vec4 text_color; |
| float is_translation_scale; |
| } |
| frame_info; |
| |
| // XYWH. |
| in vec4 atlas_glyph_bounds; |
| // XYWH |
| in vec4 glyph_bounds; |
| |
| in vec2 unit_position; |
| in vec2 glyph_position; |
| |
| out vec2 v_uv; |
| |
| IMPELLER_MAYBE_FLAT out f16vec4 v_text_color; |
| |
| mat4 basis(mat4 m) { |
| return mat4(m[0][0], m[0][1], m[0][2], 0.0, // |
| m[1][0], m[1][1], m[1][2], 0.0, // |
| m[2][0], m[2][1], m[2][2], 0.0, // |
| 0.0, 0.0, 0.0, 1.0 // |
| ); |
| } |
| |
| vec2 project(mat4 m, vec2 v) { |
| float w = v.x * m[0][3] + v.y * m[1][3] + m[3][3]; |
| vec2 result = vec2(v.x * m[0][0] + v.y * m[1][0] + m[3][0], |
| v.x * m[0][1] + v.y * m[1][1] + m[3][1]); |
| |
| // This is Skia's behavior, but it may be reasonable to allow UB for the w=0 |
| // case. |
| if (w != 0) { |
| w = 1 / w; |
| } |
| return result * w; |
| } |
| |
| void main() { |
| vec2 screen_offset = |
| round(project(frame_info.entity_transform, frame_info.offset)); |
| |
| // For each glyph, we compute two rectangles. One for the vertex positions |
| // and one for the texture coordinates (UVs). |
| vec2 uv_origin = (atlas_glyph_bounds.xy - vec2(0.5)) / frame_info.atlas_size; |
| vec2 uv_size = (atlas_glyph_bounds.zw + vec2(1)) / frame_info.atlas_size; |
| |
| // Rounding here prevents most jitter between glyphs in the run when |
| // nearest sampling. |
| mat4 basis_transform = basis(frame_info.entity_transform); |
| vec2 screen_glyph_position = |
| screen_offset + |
| round(project(basis_transform, (glyph_position + glyph_bounds.xy))); |
| |
| vec4 position; |
| if (frame_info.is_translation_scale == 1.0) { |
| // Rouding up here prevents the bounds from becoming 1 pixel too small |
| // when nearest sampling. This path breaks down for projections. |
| position = vec4( |
| screen_glyph_position + |
| ceil(project(basis_transform, unit_position * glyph_bounds.zw)), |
| 0.0, 1.0); |
| } else { |
| position = frame_info.entity_transform * |
| vec4(frame_info.offset + glyph_position + glyph_bounds.xy + |
| unit_position * glyph_bounds.zw, |
| 0.0, 1.0); |
| } |
| |
| gl_Position = frame_info.mvp * position; |
| v_uv = uv_origin + unit_position * uv_size; |
| v_text_color = frame_info.text_color; |
| } |