[Dart, Flutter] Flutter: слоёный пирог с интересной начинкой. Графика
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Write once, run anywhereЗаманчивая идея – написал код один раз, и он работает на всех платформах. По-моему, у Java это не плохо получилось. Дополнительная абстракция в виде байт-кода и JVM, отделяющая приложение от особенностей реализации платформы, помогла Java сделать своё дело. В 1995 году, когда компания Sunпровозгласила этот лозунг, ещё никто не знал что такое смартфон, а web-технологии только только начинали покорять мир. Похоже, с тех пор, многое изменилось. Количество IT-продуктов, как и их пользователей, росло огромными темпами, соответственно выросло количество технологий, инструментов и языков разработки. Акцент взаимодействия конечных пользователей с приложениями сместился в сторону web и мобильных платформ, а Android и iOS практически единолично поделили между собой мир мобильных устройств. Тем временем, можно определённо сказать, что лозунг "напиши один раз и запускай везде" приобрёл еще большую актуальность. C точки зрения бизнеса, единая кодовая база, на которой можно собрать приложение с минимальными изменениями или дополнениями для различных платформ, звучит соблазнительно. Важную роль сегодня играет UI/UX, возможности для реализации которых, нам предоставляют web и мобильные платформы. И если, в плане мультиплатформенности бизнес-логики, к Java вопросов нет, то, например, про Swing думаю можно не обсуждать. Перед техническим погружением в реализацию механизмов отрисовки во Flutter, разрешите представить своё мнение о текущем состоянии мультиплатформы.To Web or not to WebWeb-технологии не стоят на месте - HTML5, WebGL, WebRTC, WebAssembly, Progressive Web App. Казалось бы, идеальный кандидат для кроссплатформы, с виртуальной машиной (браузером), которая присутствует почти в каждом девайсе пользователя, от компьютера и телефона до часов и телевизора. Много внимания уделяется Responsive design в вебе, который помогает справиться с различными размерами экранов, а PWA делает web-приложения всё ближе к полноценным нативным приложениям.Для мобильных платформ предлагаются "промежуточные" решения, например, обернуть WebView в нативный код, чтобы притвориться нормальным приложением. PhoneGap/Cardova, Ionic и многие другие из этой группы, как говорится, - Добро пожаловать, web-разрабочики, в мир мобильных приложений! Есть желание использовать возможности платформы, которых пока нет в реализации браузера? не беда, подключаем плагины. Такой подход существует уже с 2005 года, но, по-видимому, не всё так гладко.Идея! - сказал кто-то в Facebook, - Берём полюбившийся сообществу веб-разработчиков ReactJS, а чтобы пользовательский опыт не сильно страдал, будем использовать "родные" для платформы интерфейсы взаимодействия с пользователем - на сцене появляется React Native. Xamarin c C#, KMM с Kotlin, по-моему, можно причислить к этой же когорте. Как бы там ни было - React Native, с Javascript и часто обсуждаемым "мостом", занял вполне достойное место в ряду кроссплатформенных решений.
Fresh startВ 2014 году, тихо и без пафоса, стартовал проект Sky, а через какое-то время начала появляется первая публичнаяинформация. В 2018 году, уже под именем Flutter, проект достаточно громко заявил о себе выпуском первой стабильной версии. К тому времени, у меня уже был опыт использования ReactNative, PhoneGap/Ionic и PWA - и вроде всё работает, бизнес-задачи решаются, но, где-то внутри, было труднообъяснимое чувство недосказанности по этим инструментам.Когда я впервые услышал о Flutter, первая мысль - очередная попытка "поженить" web и mobile, но, после более глубокого ознакомления с работой фреймворка, понял, что ошибался, понял, что меня не устраивало в предыдущих решениях - вот эти попытки, связать по живому mobile и web, как Франкенштейна. Flutter выглядел одновременно новым, и в тоже время знакомым решением. Новым - потому что увидел смелый шаг с отказом от полумер, в попытке "угодить и этим и этим", знакомым - поскольку, при изучении фреймворка, часто наталкивался на известные и хорошо себя показавшие подходы из веб-технологий, что не удивительно, ведь вдохновителями Flutter были и остаются Eric Siedel и Ian Hickson, которые принимали непосредственное участие в разработке Chrome(WebKit, Blink), HTML5 и других web-технологий, а разработчик Dart, принимал активное участие в разработке Javascript V8. Мне кажется, эти люди просто решили сделать шаг вперед, без оглядки на взаимные ограничения накладываемые web и mobile. В общем, просто настало время, появились люди и поддержка компании Google))Хорошая это была идея или плохая, покажет время. А пока, при упоминании Flutter, число классических мнений: «кладбище проектов google» и «зачем с нуля изобретать очередной велосипед» уменьшается пропорционально росту количеству приложений на флаттер, вакансий и популярности проекта на GitHub, а появление SwiftUI и Jetpack Compose (и более экзотических) подтверждает жизнеспособность декларативного подхода при определении UI в коде. В этой статье, я бы хотел вместе с вами погрузиться в исследование механизмов работы, одного из самых важных компонентов Flutter - отрисовка и рендеринг. И, заодно, попробуем понять, что такое шейдеры, которые последнее время на слуху в сообществе разработчиков Flutter, и почему они влияют на лаги анимации при первом старте.
Идея использования слоёв в IT-архитектуре, не нова, взять ту же Clean Architecture, которая позволяет нам разделить ответственности и взаимодействие между слоями. Flutter с самого начала своей истории активно использует этот подход. В первом приближении Flutter UI Toolkit делится на 3 слоя:
Application Code - код написанный разработчиком FlutterFlutter Framework - написан на Dart, содержит большое количество базовых виджетов для построения визуального интерфейса взаимодействия с пользователем, механизм построения и компоновки виджетов, создание команд отрисовки готовой сцены для передачи на уровень Engine, инструменты тестирования и отладки.Flutter Engine - платформонезависимая часть Flutter, написанная на C/C++, фактически представляет из себя динамическую библиотеку, подключаемую при старте приложения. Поставляется в скомпилированном виде на машине разработчика при установке Flutter. Содержит виртуальную машину Dart, библиотеку Skia, код рендеринга, код взаимодействия с нижнем уровнем платформы.Embedder - часть реализации, зависящая от платформы для которой будет собираться приложение, отвечает за: подготовку и предоставление поверхности для рисования в Engine, сигнал VSYNC(синхронизация отрисовки кадра), обслуживание событий интерфейса пользователя, создание потоков и внутренних очередей событий.FrameworkЭто первый слой, с которым начинает "общаться" Flutter разработчик. А если быть более точным, и принять во внимание отличие понятий "библиотека" и "фреймворк", Flutter Framework, управляет написанным нами кодом. На языке Dart, создается декларативное описание интерфейса в виде древовидной компоновки объектов Widget, и уже сам фреймворк вызывает методы отрисовки или перестроения дерева виджетов.Сам фреймворк написан полностью на языке Dart, и компилируется вместе с нашей программой в момент сборки приложения. Одна из основных задач фреймворка - преобразовать описаный нами декларативный интерфейс, в так называемое сцену, и отправить на слой ниже в Engine.Фреймворк также внутри разделен на слои, каждый со своей зоной ответственности.
- Уровень виджетов - использует программист Flutter при разработке(чуть ниже есть уровень Elements, его пропустим, полное описание принципа построения виджетов будет дано по ссылке ниже)
- Уровень рендеринга - сам фреймворк, под капотом, использует так называемые RenderObject, которые формируются из дерева виджетов
- Уровень отрисовки - здесь на основе дерева RenderObject формируются команды отрисовки для передачи в Engine.
Для исследования, возьмем простейший пример, состоящий из нескольких виджетов с простой конфигурацией.box_example.dart
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.blueAccent),
borderRadius: const BorderRadius.all(Radius.circular(30)),
),
padding: EdgeInsets.all(30),
child: Container(
color: Colors.redAccent,
),
),
);
}
}
Результат работы box_example.dartПо коду нашего приложения видно, что мы, для отображения нашей композиции, не указывали конкретных координат отрисовки, положение и направлений линий и закраски, нет каких-либо команд рисования, т.е. в данном случае используем полностью декларативный, описательный подход. Также во Flutter, в качестве размеров используются логические пиксели, которые не зависят от реального разрешения и плотности экрана и позволяют оперировать практически одинаковыми визуальными размерами на разных устройствах.После запуска приложения происходит инициализация так называемых Bindings, которые связывают фреймворк с нижним уровнем Engine. SchedulerBinding Отвечает, в том числе, за обработку и формирование вызовов сигнализирующих о текущем статусе построения фрейма (BeginFrame, DrawFrame)WidgetsBinding отвечает за перестроение дерева виджетов, построение elements и RenderObejctRenderBinding После построения дерева элементов, RenderObject и их взаимосвязей, в работу включается слой фреймворка Rendering, который выполняет несколько этапов
- Layout - производится расчет размеров и положения всех RenderObject
- CompositingBits - обновляется информация об объетках использующих композицию
- Paint - отрисовка экрана. Здесь задействована Skia, через использование PictureLayer, PictureRecorder и Canvas, RenderObjects отрисовываютс себя с помощью этих элементов, но, на самом деле фактически просто формируется список команд отрисовки и применения слоёв.
- Compositing - создается и строится сцена Scene с учетом всех слоёв их композиции и команд отрисовки на этих слоях.
Подготовка сцены перед передачей на отрисовку в EngineFlutter EngineПосле того как сцена(Scene) со всеми командами отрисовки подготовлена, она передается на уровень Engine с помощью метода render. Затем из сцены извлекается LayerTree, производятся необходимые предварительные действия и проверка дерева. На данном этапе, работа в потоке UI завершается, а LayerTree передается на обработку в Raster поток.
При запуске Flutter приложения, инициализируются четыре потока
UI Thread - основная программа пользователя и код фреймворка на Dart
Raster Thread - поток работы с OpenGL и отрисовкой на GPU
IO Thread - вспомогательный поток для Raster thread, обработка assets
Platform Thread - платформозависимый код приложения
Процессом непосредственной отрисовки на экран устройства управляет класс Rasterizer. Метод Draw этого класса запускается в отельном потоке Raster. Сначала в работу вступает композитор Flow, он разворачивает дерево и подготавливает слои дерева (LayerTree:Preroll) к отрисовке, а после выполняет команды отрисовки Skia, которые находятся в дереве (LayerTree:paint), замечу, в данном случае непосредственно отрисовки на экране не происходит, по сути собирается очередь из так называемых объектов операций Skia (см. addDrawOp)
В методе SkCanvas:Flush производится выполнение операций Skia, из очереди, которая была подготовлена на предыдущем этапе, эти операции выполняют непосредственную отрисовку в OpenGL буфер. После отрисовки вызывается метод платформы SwapBuffer, изображение появляется на экране, а цикл рендеринга фрейма в потоке GPU завершается.
По временному графику видно, что время подготовки сцены в UI потоке и её последующей композиции в Raster потоке мало, по сравнению с временем выполнения непосредственной отрисовки в SkCanvas:Flush. В нашем случае простой сцены это в пределах нормы, но при сложной сцене время отрисовки может значительно возрасти. Как правило, это связно с подготовкой программ для графического процессора по каждой из операций Skia. Обратите внимание на пару GrGlProgram в операциях отрисовки прямоугольников из нашего примера - FillRectOp на графике, это как раз место где генерируются и компилируются программы для GPU. Shaders
Skia, как и большинство программ использующих OpenGL/Metal при рендеринг, не использует команды отрисовки отдельных точек, линий, полигонов и их закрашивание. Для отрисовки элемента сначала создаются буферы с вершинами и их координатами(vertex), а также генерируются так называемые шейдеры. Шейдеры - по сути программы, которые работают на процессорах GPU, и параллельно обрабатывают координаты и цвета точек, которые им были предварительно переданы через буферы. В Skia, для рендеринга элементов используются 2 типа шейдеров - это вершинные(vertex) и пиксельные(fragment). Вершинные работают с координатами(например могут применять матрицы трансформации, проекции), пиксельные управляют цветом каждого пикселя перед выдачей на экран устройства.Существуют несколько вариантов языков описания шейдеров, в большинстве своём, это Си-подобные языки (Metal например использует спецификацию С++14). Чтобы абстрагироваться от разных вариантов языков, Skia, под капотом, использует свой формат SkSL, который фактически унаследован от OpenGL GLSL.Ниже приведены примеры шейдеров, которые создаются для нашей простейшей программы, с изображением рамки со скруглёнными углами и закрашенного квадрата. Вершинный и пиксельный шейдеры сгенерированые Skia для отрисовки квадрата
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
in float2 position;
in half4 color;
noperspective out half4 vcolor_Stage0;
void main()
{
// Primitive Processor QuadPerEdgeAAGeometryProcessor
vcolor_Stage0 = color;
sk_Position = float4(position.x , position.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
noperspective in half4 vcolor_Stage0;
out half4 sk_FragColor;
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, QuadPerEdgeAAGeometryProcessor
outputColor_Stage0 = vcolor_Stage0;
outputCoverage_Stage0 = half4(1);
}
{
// Xfer Processor: Porter Duff
sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
}
}
Вершинный и пиксельный шейдеры сгенерированные Skia для отрисовки рамки
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
in float2 position;
in half4 color;
noperspective out half4 vcolor_Stage0;
void main()
{
// Primitive Processor QuadPerEdgeAAGeometryProcessor
vcolor_Stage0 = color;
sk_Position = float4(position.x , position.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 uinnerRect_Stage1_c0;
uniform half2 uradiusPlusHalf_Stage1_c0;
uniform float4 uinnerRect_Stage1_c0_c0;
uniform half2 uradiusPlusHalf_Stage1_c0_c0;
noperspective in half4 vcolor_Stage0;
out half4 sk_FragColor;
half4 CircularRRect_Stage1_c0_c0(half4 _input)
{
float2 dxy0 = uinnerRect_Stage1_c0_c0.LT - sk_FragCoord.xy;
float2 dxy1 = sk_FragCoord.xy - uinnerRect_Stage1_c0_c0.RB;
float2 dxy = max(max(dxy0, dxy1), 0.0);
half alpha = half(saturate(uradiusPlusHalf_Stage1_c0_c0.x - length(dxy)));
alpha = 1.0 - alpha;
return _input * alpha;
}
inline half4 CircularRRect_Stage1_c0(half4 _input)
{
float2 dxy0 = uinnerRect_Stage1_c0.LT - sk_FragCoord.xy;
float2 dxy1 = sk_FragCoord.xy - uinnerRect_Stage1_c0.RB;
float2 dxy = max(max(dxy0, dxy1), 0.0);
half alpha = half(saturate(uradiusPlusHalf_Stage1_c0.x - length(dxy)));
return CircularRRect_Stage1_c0_c0(_input) * alpha;
}
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, QuadPerEdgeAAGeometryProcessor
outputColor_Stage0 = vcolor_Stage0;
outputCoverage_Stage0 = half4(1);
}
half4 output_Stage1;
output_Stage1 = CircularRRect_Stage1_c0(outputCoverage_Stage0);
{
// Xfer Processor: Porter Duff
sk_FragColor = outputColor_Stage0 * output_Stage1;
}
}
Т.е. в нашем примере сгенерировано всего 2 шейдера, но если мы возьмем классический пример приложения каунтера во Flutter:
шейдеров гораздо больше (с учётом эффекта при нажатии кнопки) Шейдеры классического примера с каунтером
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
in float2 inPosition;
in half4 inColor;
in half3 inShadowParams;
noperspective out half3 vinShadowParams_Stage0;
noperspective out half4 vinColor_Stage0;
void main()
{
// Primitive Processor RRectShadow
vinShadowParams_Stage0 = inShadowParams;
vinColor_Stage0 = inColor;
float2 _tmp_0_inPosition = inPosition;
sk_Position = float4(_tmp_0_inPosition.x , _tmp_0_inPosition.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform sampler2D uTextureSampler_0_Stage0;
noperspective in half3 vinShadowParams_Stage0;
noperspective in half4 vinColor_Stage0;
out half4 sk_FragColor;
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, RRectShadow
half3 shadowParams;
shadowParams = vinShadowParams_Stage0;
outputColor_Stage0 = vinColor_Stage0;
half d = length(shadowParams.xy);
float2 uv = float2(shadowParams.z * (1.0 - d), 0.5);
half factor = sample(uTextureSampler_0_Stage0, uv).000r.a;
outputCoverage_Stage0 = half4(factor);
}
{
// Xfer Processor: Porter Duff
sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
}
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
in float2 position;
in half4 color;
noperspective out half4 vcolor_Stage0;
void main()
{
// Primitive Processor QuadPerEdgeAAGeometryProcessor
vcolor_Stage0 = color;
sk_Position = float4(position.x , position.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
noperspective in half4 vcolor_Stage0;
out half4 sk_FragColor;
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, QuadPerEdgeAAGeometryProcessor
outputColor_Stage0 = vcolor_Stage0;
outputCoverage_Stage0 = half4(1);
}
{
// Xfer Processor: Porter Duff
sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
}
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
uniform float3x3 ulocalMatrix_Stage0;
in float2 inPosition;
in half4 inColor;
in float4 inCircleEdge;
noperspective out float4 vinCircleEdge_Stage0;
noperspective out half4 vinColor_Stage0;
void main()
{
// Primitive Processor CircleGeometryProcessor
vinCircleEdge_Stage0 = inCircleEdge;
vinColor_Stage0 = inColor;
float2 _tmp_0_inPosition = inPosition;
float2 _tmp_1_inPosition = (ulocalMatrix_Stage0 * inPosition.xy1).xy;
sk_Position = float4(_tmp_0_inPosition.x , _tmp_0_inPosition.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
noperspective in float4 vinCircleEdge_Stage0;
noperspective in half4 vinColor_Stage0;
out half4 sk_FragColor;
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, CircleGeometryProcessor
float4 circleEdge;
circleEdge = vinCircleEdge_Stage0;
outputColor_Stage0 = vinColor_Stage0;
float d = length(circleEdge.xy);
half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));
half edgeAlpha = saturate(distanceToOuterEdge);
outputCoverage_Stage0 = half4(edgeAlpha);
}
{
// Xfer Processor: Porter Duff
sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
}
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
in float2 position;
in half4 inColor;
noperspective out half4 vcolor_Stage0;
void main()
{
// Primitive Processor VerticesGP
half4 color = inColor;
color = color.bgra;
color = color;
color = half4(color.rgb * color.a, color.a);
vcolor_Stage0 = color;
float2 _tmp_0_position = position;
sk_Position = float4(_tmp_0_position.x , _tmp_0_position.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform half4 ucolor_Stage1_c0_c0;
noperspective in half4 vcolor_Stage0;
out half4 sk_FragColor;
half4 ConstColorProcessor_Stage1_c0_c0(half4 _input)
{
return ucolor_Stage1_c0_c0;
}
half4 BlurredEdgeFragmentProcessor_Stage1_c0_c1(half4 _input)
{
half inputAlpha = _input.w;
half factor = 1.0 - inputAlpha;
@switch (0)
{
case 0: factor = exp((-factor * factor) * 4.0) - 0.017999999225139618;
break;
case 1: factor = smoothstep(1.0, 0.0, factor);
break;
}
return half4(factor);
}
inline half4 Blend_Stage1_c0(half4 _input)
{
// Blend mode: Modulate (SkMode behavior)
return blend_modulate(ConstColorProcessor_Stage1_c0_c0(half4(1)), BlurredEdgeFragmentProcessor_Stage1_c0_c1(_input));
}
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, VerticesGP
outputColor_Stage0 = vcolor_Stage0;
outputCoverage_Stage0 = half4(1);
}
half4 output_Stage1;
output_Stage1 = Blend_Stage1_c0(outputColor_Stage0);
{
// Xfer Processor: Porter Duff
sk_FragColor = output_Stage1 * outputCoverage_Stage0;
}
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
uniform float2 uAtlasSizeInv_Stage0;
in float2 inPosition;
in half4 inColor;
in ushort2 inTextureCoords;
noperspective out float2 vTextureCoords_Stage0;
noperspective out float vTexIndex_Stage0;
noperspective out half4 vinColor_Stage0;
void main()
{
// Primitive Processor Texture
int texIdx = 0;
float2 unormTexCoords = float2(inTextureCoords.x, inTextureCoords.y);
vTextureCoords_Stage0 = unormTexCoords * uAtlasSizeInv_Stage0;
vTexIndex_Stage0 = float(texIdx);
vinColor_Stage0 = inColor;
float2 _tmp_0_inPosition = inPosition;
sk_Position = float4(inPosition.x , inPosition.y, 0, 1);
}
}#extension GL_NV_shader_noperspective_interpolation: require
uniform sampler2D uTextureSampler_0_Stage0;
noperspective in float2 vTextureCoords_Stage0;
noperspective in float vTexIndex_Stage0;
noperspective in half4 vinColor_Stage0;
out half4 sk_FragColor;
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, Texture
outputColor_Stage0 = vinColor_Stage0;
half4 texColor;
{
texColor = sample(uTextureSampler_0_Stage0, vTextureCoords_Stage0).rrrr;
}
outputCoverage_Stage0 = texColor;
}
{
// Xfer Processor: Porter Duff
sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
}
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
uniform float4 uviewMatrix_Stage0;
in float2 position;
in half4 inColor;
noperspective out half4 vcolor_Stage0;
void main()
{
// Primitive Processor VerticesGP
half4 color = inColor;
color = color.bgra;
color = color;
color = half4(color.rgb * color.a, color.a);
vcolor_Stage0 = color;
float2 _tmp_0_position = uviewMatrix_Stage0.xz * position + uviewMatrix_Stage0.yw;
sk_Position = float4(_tmp_0_position.x , _tmp_0_position.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform half4 ucolor_Stage1_c0_c0;
noperspective in half4 vcolor_Stage0;
out half4 sk_FragColor;
half4 ConstColorProcessor_Stage1_c0_c0(half4 _input)
{
return ucolor_Stage1_c0_c0;
}
half4 BlurredEdgeFragmentProcessor_Stage1_c0_c1(half4 _input)
{
half inputAlpha = _input.w;
half factor = 1.0 - inputAlpha;
@switch (0)
{
case 0: factor = exp((-factor * factor) * 4.0) - 0.017999999225139618;
break;
case 1: factor = smoothstep(1.0, 0.0, factor);
break;
}
return half4(factor);
}
inline half4 Blend_Stage1_c0(half4 _input)
{
// Blend mode: Modulate (SkMode behavior)
return blend_modulate(ConstColorProcessor_Stage1_c0_c0(half4(1)), BlurredEdgeFragmentProcessor_Stage1_c0_c1(_input));
}
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, VerticesGP
outputColor_Stage0 = vcolor_Stage0;
outputCoverage_Stage0 = half4(1);
}
half4 output_Stage1;
output_Stage1 = Blend_Stage1_c0(outputColor_Stage0);
{
// Xfer Processor: Porter Duff
sk_FragColor = output_Stage1 * outputCoverage_Stage0;
}
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
uniform float3x3 ulocalMatrix_Stage0;
in float2 inPosition;
in half4 inColor;
in float4 inCircleEdge;
noperspective out float4 vinCircleEdge_Stage0;
noperspective out half4 vinColor_Stage0;
void main()
{
// Primitive Processor CircleGeometryProcessor
vinCircleEdge_Stage0 = inCircleEdge;
vinColor_Stage0 = inColor;
float2 _tmp_0_inPosition = inPosition;
float2 _tmp_1_inPosition = (ulocalMatrix_Stage0 * inPosition.xy1).xy;
sk_Position = float4(_tmp_0_inPosition.x , _tmp_0_inPosition.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float3x3 umatrix_Stage1_c0_c0_c0;
uniform sampler2D uTextureSampler_0_Stage1;
noperspective in float4 vinCircleEdge_Stage0;
noperspective in half4 vinColor_Stage0;
out half4 sk_FragColor;
half4 TextureEffect_Stage1_c0_c0_c0_c0(half4 _input, float2 _coords)
{
return sample(uTextureSampler_0_Stage1, _coords).000r;
}
half4 MatrixEffect_Stage1_c0_c0_c0(half4 _input, float2 _coords)
{
return TextureEffect_Stage1_c0_c0_c0_c0(_input, ((umatrix_Stage1_c0_c0_c0) * _coords.xy1).xy);
}
half4 DeviceSpaceEffect_Stage1_c0_c0(half4 _input)
{
return MatrixEffect_Stage1_c0_c0_c0(_input, sk_FragCoord.xy);
}
inline half4 Blend_Stage1_c0(half4 _input)
{
// Blend mode: DstIn (Compose-One behavior)
return blend_dst_in(DeviceSpaceEffect_Stage1_c0_c0(half4(1)), _input);
}
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, CircleGeometryProcessor
float4 circleEdge;
circleEdge = vinCircleEdge_Stage0;
outputColor_Stage0 = vinColor_Stage0;
float d = length(circleEdge.xy);
half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));
half edgeAlpha = saturate(distanceToOuterEdge);
outputCoverage_Stage0 = half4(edgeAlpha);
}
half4 output_Stage1;
output_Stage1 = Blend_Stage1_c0(outputCoverage_Stage0);
{
// Xfer Processor: Porter Duff
sk_FragColor = outputColor_Stage0 * output_Stage1;
}
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust;
in float4 radii_selector;
in float4 corner_and_radius_outsets;
in float4 aa_bloat_and_coverage;
in float4 skew;
in float2 translate;
in float4 radii_x;
in float4 radii_y;
in half4 color;
noperspective out half4 vcolor_Stage0;
noperspective out float2 varccoord_Stage0;
void main()
{
// Primitive Processor GrFillRRectOp::Processor
vcolor_Stage0 = color;
float2 corner = corner_and_radius_outsets.xy;
float2 radius_outset = corner_and_radius_outsets.zw;
float2 aa_bloat_direction = aa_bloat_and_coverage.xy;
float coverage = aa_bloat_and_coverage.z;
float is_linear_coverage = aa_bloat_and_coverage.w;
float2 pixellength = inversesqrt(float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw)));
float4 normalized_axis_dirs = skew * pixellength.xyxy;
float2 axiswidths = (abs(normalized_axis_dirs.xy) + abs(normalized_axis_dirs.zw));
float2 aa_bloatradius = axiswidths * pixellength * .5;
float4 radii_and_neighbors = radii_selector* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx);
float2 radii = radii_and_neighbors.xy;
float2 neighbor_radii = radii_and_neighbors.zw;
if (any(greaterThan(aa_bloatradius, float2(1))))
{
corner = max(abs(corner), aa_bloatradius) * sign(corner);
coverage /= max(aa_bloatradius.x, 1) * max(aa_bloatradius.y, 1);
radii = float2(0);
}
if (any(lessThan(radii, aa_bloatradius * 1.25)))
{
radii = aa_bloatradius;
radius_outset = floor(abs(radius_outset)) * radius_outset;
is_linear_coverage = 1;
}
else
{
radii = clamp(radii, pixellength, 2 - pixellength);
neighbor_radii = clamp(neighbor_radii, pixellength, 2 - pixellength);
float2 spacing = 2 - radii - neighbor_radii;
float2 extra_pad = max(pixellength * .0625 - spacing, float2(0));
radii -= extra_pad * .5;
}
float2 aa_outset = aa_bloat_direction.xy * aa_bloatradius;
float2 vertexpos = corner + radius_outset * radii + aa_outset;
float2x2 skewmatrix = float2x2(skew.xy, skew.zw);
float2 devcoord = vertexpos * skewmatrix + translate;
if (0 != is_linear_coverage)
{
varccoord_Stage0.xy = float2(0, coverage);
}
else
{
float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner;
varccoord_Stage0.xy = float2(arccoord.x+1, arccoord.y);
}
sk_Position = float4(devcoord.x , devcoord.y, 0, 1);
}
#extension GL_NV_shader_noperspective_interpolation: require
uniform float3x3 umatrix_Stage1_c0_c0_c0;
uniform sampler2D uTextureSampler_0_Stage1;
noperspective in half4 vcolor_Stage0;
noperspective in float2 varccoord_Stage0;
out half4 sk_FragColor;
half4 TextureEffect_Stage1_c0_c0_c0_c0(half4 _input, float2 _coords)
{
return sample(uTextureSampler_0_Stage1, _coords).000r;
}
half4 MatrixEffect_Stage1_c0_c0_c0(half4 _input, float2 _coords)
{
return TextureEffect_Stage1_c0_c0_c0_c0(_input, ((umatrix_Stage1_c0_c0_c0) * _coords.xy1).xy);
}
half4 DeviceSpaceEffect_Stage1_c0_c0(half4 _input)
{
return MatrixEffect_Stage1_c0_c0_c0(_input, sk_FragCoord.xy);
}
inline half4 Blend_Stage1_c0(half4 _input)
{
// Blend mode: DstIn (Compose-One behavior)
return blend_dst_in(DeviceSpaceEffect_Stage1_c0_c0(half4(1)), _input);
}
void main()
{
half4 outputColor_Stage0;
half4 outputCoverage_Stage0;
{
// Stage 0, GrFillRRectOp::Processor
outputColor_Stage0 = vcolor_Stage0;
float x_plus_1=varccoord_Stage0.x, y=varccoord_Stage0.y;
half coverage;
if (0 == x_plus_1)
{
coverage = half(y);
}
else
{
float fn = x_plus_1 * (x_plus_1 - 2);
fn = fma(y,y, fn);
float fnwidth = fwidth(fn);
half d = half(fn/fnwidth);
coverage = clamp(.5 - d, 0, 1);
}
outputCoverage_Stage0 = half4(coverage);
}
half4 output_Stage1;
output_Stage1 = Blend_Stage1_c0(outputCoverage_Stage0);
{
// Xfer Processor: Porter Duff
sk_FragColor = outputColor_Stage0 * output_Stage1;
}
}
Shader compilation jankПоскольку шейдеры, по сути, просто программы на исходном языке, как и обычные программы на С, перед использованием на GPU их необходимо скомпилировать. Конечно для компиляции требуется время, поэтому, как правило, существует два варианта - или компилировать непосредственно перед использованием в процессе рендеринга очередного кадра, или компилировать их предварительно, перед запуском основного кода приложения - подход часто используемый, например, для игровых приложений.Здесь и проявляется проблема с пропуском кадров во время первых анимаций, которую сейчас горячо обсуждают в сообществе Flutter. По-умолчанию, в Skia, шейдеры компилируются перед использованием, если размер шейдеров небольшой, то это никак не влияет на поведение, а если, например, при анимации роута, на новом экране будет использоваться разные элементы, которые потребуют значительное время на компиляцию шейдеров, то в это время текущая анимация может подлагивать.
Можно ли предварительно скомпилировать все возможные шейдеры, перед запуском программы?
Шейдеры в Skia генерируются в зависимости от конфигурации операции - например, закрашенный прямоугольник или только рамка, стиль отрисовки, применение матриц трансформаций - соответственно это порождает много возможных вариантов для каждого примитива (насколько много, на текущий момент, я сказать не могу, предположительно, с учетом всех примитивов, речь идет о сотнях) Каждой программе присваивается уникальный ключ, и после компиляции шейдера, происходит сохранение результатов компиляции по этому ключу. Поэтому в следующий раз, при формировании такой же программы и условии наличия в кэше ключа, повторная компиляция будет не нужна, поэтому данная проблема проявлятся только при первой анимации.Для решения проблемы компиляции большого количества шейдеров в OpenGL, команда Flutter и Skia, реализовала метод предварительной записи шейдеров - так называемый "прогрев шейдеров". Суть метода такова - на машине разработчика запускается приложение с ключом, который позволяет сохранять используемые в Skia шейдеры. После запуска приложения, мы пробуем все варианты взаимодействия с интерфейсом, при этом шейдеры запоминаются и сохраняются на диск. Приложение собирается с этими шейдерами, и при старте на устройстве пользователя, они считываются и компилируются перед запуском основного кода.Сам формат в котором Flutter сохраняет шейдеры закодирован с помощью base64, поэтому для удобства анализа я написал небольшую программу, которая может декодировать в читабельный вид. Ссылка на программу здесь. Плагин VSCode для подсветки синтаксиса здесь.MetalВыход metal и добровольно-принудительный переход на эту библиотеку в приложениях iOS, обострил проблему компиляции с новой силой. Дело в том, что предлагаемый метод "прогрева шейдеров", был реализован только на уровне OpenGL. Кроме того, текущий подход при компиляции шейдеров Metal MLSL, в некоторых случаях занимает больше времени, чем, при равных условиях, компиляция шейдеров OpenGL GLSL, что также повлияло на юзер экспириенс и справедливые возмущения разработчиков и бизнеса.Подход к обработке скомпилированных объектов и их применении в пайплайне рендеринга Metal, немного отличаются от подхода в OpenGL, но, в любом случае методы предварительной компиляции описаны и доступны для использования. Также осенью прошлого года, Apple на WWDC2020 провела презентацию по использованию прекомпиляции и бинарных файлов при построении пайплайна GPU.По-моему мнению, эта проблема "первых кадров" лежит не в технической, а организационной плоскости, и команда Flutter не придавала должного значения к анализу данной проблемы, а сама проблема решалась без привлечения специалистов по Metal. И только накал страстей и давление сообщества, помогло сдвинуть дело с мертвой точки, и последние недели на GitHub Skia, стали появляется изменения связанные с оптимизацией бинарных архивов Metal и подготовкой библиотек шейдеров.Также необходимо упомянуть, что команда Flutter рассматривает вариант "прогрева шейдеров" как временный workaround. Конечная цель - полностью избавить разработчика от переживаний и дополнительных действий связанных с "прогревом" шейдеров.Futter For WebБуквально пара слов про рендеринг на Canvas в вебе. Для рендеринга, HTML элемент сanvas, используется только для инициализации WebGL (по сути, калька с OpenGLES). Skia, как известно, хорошо работает с OpenGL. Поэтому фактически библиотека Skia написанная на C++, компилируется в WebAssembly с помощью Emscripten + добавляются некоторые байндинги и подключается к проекту.Show must go onКак видно Flutter пытается откусить достаточно жирный кусок у платформы. От платформы по-сути требуется только поверхность для рисования, и некоторые системные вещи. Флаттер уже стабильно поддерживает Android/iOS/Linux/Windows.Конечно, такой кардинальный подход, который выбрала Flutter команда, не обходится без трудностей, но куда ж без них, без трудностей))SourcesFlutter architectural overview
Inside Flutter
Skia
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Разработка мобильных приложений, Dart, Flutter] Dart 2.12: Sound null safety и Dart FFI отправлены на стабильный канал (перевод)
- [Программирование, Dart] Ускоряем Dart. Нативно, недорого
- [Разработка мобильных приложений, Flutter] С чего начать изучение Flutter в 2021 году (перевод)
- [Flutter] Flutter вот-вот завоюет Web (перевод)
- [Программирование, Разработка мобильных приложений, Dart, Flutter] Flutter 2: что нового (перевод)
- [Flutter] 2 шага к построению адаптивной верстки Flutter-приложения
- Google представил фреймворк Flutter 2 и язык Dart 2.12
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android] Кроссплатформенная мобильная разработка: история вопроса
- [Разработка веб-сайтов, Программирование, Dart, Flutter] Подготовка экосистем Dart и Flutter к переходу на null safety (перевод)
- [Разработка мобильных приложений] Как выбрать мобильную кросс-платформу в 2021 году (перевод)
Теги для поиска: #_dart, #_flutter, #_flutter, #_dart, #_skia, #_blog_kompanii_liga_stavok (
Блог компании Лига Ставок
), #_dart, #_flutter
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 01:05
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Write once, run anywhereЗаманчивая идея – написал код один раз, и он работает на всех платформах. По-моему, у Java это не плохо получилось. Дополнительная абстракция в виде байт-кода и JVM, отделяющая приложение от особенностей реализации платформы, помогла Java сделать своё дело. В 1995 году, когда компания Sunпровозгласила этот лозунг, ещё никто не знал что такое смартфон, а web-технологии только только начинали покорять мир. Похоже, с тех пор, многое изменилось. Количество IT-продуктов, как и их пользователей, росло огромными темпами, соответственно выросло количество технологий, инструментов и языков разработки. Акцент взаимодействия конечных пользователей с приложениями сместился в сторону web и мобильных платформ, а Android и iOS практически единолично поделили между собой мир мобильных устройств. Тем временем, можно определённо сказать, что лозунг "напиши один раз и запускай везде" приобрёл еще большую актуальность. C точки зрения бизнеса, единая кодовая база, на которой можно собрать приложение с минимальными изменениями или дополнениями для различных платформ, звучит соблазнительно. Важную роль сегодня играет UI/UX, возможности для реализации которых, нам предоставляют web и мобильные платформы. И если, в плане мультиплатформенности бизнес-логики, к Java вопросов нет, то, например, про Swing думаю можно не обсуждать. Перед техническим погружением в реализацию механизмов отрисовки во Flutter, разрешите представить своё мнение о текущем состоянии мультиплатформы.To Web or not to WebWeb-технологии не стоят на месте - HTML5, WebGL, WebRTC, WebAssembly, Progressive Web App. Казалось бы, идеальный кандидат для кроссплатформы, с виртуальной машиной (браузером), которая присутствует почти в каждом девайсе пользователя, от компьютера и телефона до часов и телевизора. Много внимания уделяется Responsive design в вебе, который помогает справиться с различными размерами экранов, а PWA делает web-приложения всё ближе к полноценным нативным приложениям.Для мобильных платформ предлагаются "промежуточные" решения, например, обернуть WebView в нативный код, чтобы притвориться нормальным приложением. PhoneGap/Cardova, Ionic и многие другие из этой группы, как говорится, - Добро пожаловать, web-разрабочики, в мир мобильных приложений! Есть желание использовать возможности платформы, которых пока нет в реализации браузера? не беда, подключаем плагины. Такой подход существует уже с 2005 года, но, по-видимому, не всё так гладко.Идея! - сказал кто-то в Facebook, - Берём полюбившийся сообществу веб-разработчиков ReactJS, а чтобы пользовательский опыт не сильно страдал, будем использовать "родные" для платформы интерфейсы взаимодействия с пользователем - на сцене появляется React Native. Xamarin c C#, KMM с Kotlin, по-моему, можно причислить к этой же когорте. Как бы там ни было - React Native, с Javascript и часто обсуждаемым "мостом", занял вполне достойное место в ряду кроссплатформенных решений. Fresh startВ 2014 году, тихо и без пафоса, стартовал проект Sky, а через какое-то время начала появляется первая публичнаяинформация. В 2018 году, уже под именем Flutter, проект достаточно громко заявил о себе выпуском первой стабильной версии. К тому времени, у меня уже был опыт использования ReactNative, PhoneGap/Ionic и PWA - и вроде всё работает, бизнес-задачи решаются, но, где-то внутри, было труднообъяснимое чувство недосказанности по этим инструментам.Когда я впервые услышал о Flutter, первая мысль - очередная попытка "поженить" web и mobile, но, после более глубокого ознакомления с работой фреймворка, понял, что ошибался, понял, что меня не устраивало в предыдущих решениях - вот эти попытки, связать по живому mobile и web, как Франкенштейна. Flutter выглядел одновременно новым, и в тоже время знакомым решением. Новым - потому что увидел смелый шаг с отказом от полумер, в попытке "угодить и этим и этим", знакомым - поскольку, при изучении фреймворка, часто наталкивался на известные и хорошо себя показавшие подходы из веб-технологий, что не удивительно, ведь вдохновителями Flutter были и остаются Eric Siedel и Ian Hickson, которые принимали непосредственное участие в разработке Chrome(WebKit, Blink), HTML5 и других web-технологий, а разработчик Dart, принимал активное участие в разработке Javascript V8. Мне кажется, эти люди просто решили сделать шаг вперед, без оглядки на взаимные ограничения накладываемые web и mobile. В общем, просто настало время, появились люди и поддержка компании Google))Хорошая это была идея или плохая, покажет время. А пока, при упоминании Flutter, число классических мнений: «кладбище проектов google» и «зачем с нуля изобретать очередной велосипед» уменьшается пропорционально росту количеству приложений на флаттер, вакансий и популярности проекта на GitHub, а появление SwiftUI и Jetpack Compose (и более экзотических) подтверждает жизнеспособность декларативного подхода при определении UI в коде. В этой статье, я бы хотел вместе с вами погрузиться в исследование механизмов работы, одного из самых важных компонентов Flutter - отрисовка и рендеринг. И, заодно, попробуем понять, что такое шейдеры, которые последнее время на слуху в сообществе разработчиков Flutter, и почему они влияют на лаги анимации при первом старте. Идея использования слоёв в IT-архитектуре, не нова, взять ту же Clean Architecture, которая позволяет нам разделить ответственности и взаимодействие между слоями. Flutter с самого начала своей истории активно использует этот подход. В первом приближении Flutter UI Toolkit делится на 3 слоя: Application Code - код написанный разработчиком FlutterFlutter Framework - написан на Dart, содержит большое количество базовых виджетов для построения визуального интерфейса взаимодействия с пользователем, механизм построения и компоновки виджетов, создание команд отрисовки готовой сцены для передачи на уровень Engine, инструменты тестирования и отладки.Flutter Engine - платформонезависимая часть Flutter, написанная на C/C++, фактически представляет из себя динамическую библиотеку, подключаемую при старте приложения. Поставляется в скомпилированном виде на машине разработчика при установке Flutter. Содержит виртуальную машину Dart, библиотеку Skia, код рендеринга, код взаимодействия с нижнем уровнем платформы.Embedder - часть реализации, зависящая от платформы для которой будет собираться приложение, отвечает за: подготовку и предоставление поверхности для рисования в Engine, сигнал VSYNC(синхронизация отрисовки кадра), обслуживание событий интерфейса пользователя, создание потоков и внутренних очередей событий.FrameworkЭто первый слой, с которым начинает "общаться" Flutter разработчик. А если быть более точным, и принять во внимание отличие понятий "библиотека" и "фреймворк", Flutter Framework, управляет написанным нами кодом. На языке Dart, создается декларативное описание интерфейса в виде древовидной компоновки объектов Widget, и уже сам фреймворк вызывает методы отрисовки или перестроения дерева виджетов.Сам фреймворк написан полностью на языке Dart, и компилируется вместе с нашей программой в момент сборки приложения. Одна из основных задач фреймворка - преобразовать описаный нами декларативный интерфейс, в так называемое сцену, и отправить на слой ниже в Engine.Фреймворк также внутри разделен на слои, каждый со своей зоной ответственности.
import 'package:flutter/material.dart';
void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: Container( width: 200, height: 200, decoration: BoxDecoration( border: Border.all(color: Colors.blueAccent), borderRadius: const BorderRadius.all(Radius.circular(30)), ), padding: EdgeInsets.all(30), child: Container( color: Colors.redAccent, ), ), ); } } Результат работы box_example.dartПо коду нашего приложения видно, что мы, для отображения нашей композиции, не указывали конкретных координат отрисовки, положение и направлений линий и закраски, нет каких-либо команд рисования, т.е. в данном случае используем полностью декларативный, описательный подход. Также во Flutter, в качестве размеров используются логические пиксели, которые не зависят от реального разрешения и плотности экрана и позволяют оперировать практически одинаковыми визуальными размерами на разных устройствах.После запуска приложения происходит инициализация так называемых Bindings, которые связывают фреймворк с нижним уровнем Engine. SchedulerBinding Отвечает, в том числе, за обработку и формирование вызовов сигнализирующих о текущем статусе построения фрейма (BeginFrame, DrawFrame)WidgetsBinding отвечает за перестроение дерева виджетов, построение elements и RenderObejctRenderBinding После построения дерева элементов, RenderObject и их взаимосвязей, в работу включается слой фреймворка Rendering, который выполняет несколько этапов
Подготовка сцены перед передачей на отрисовку в EngineFlutter EngineПосле того как сцена(Scene) со всеми командами отрисовки подготовлена, она передается на уровень Engine с помощью метода render. Затем из сцены извлекается LayerTree, производятся необходимые предварительные действия и проверка дерева. На данном этапе, работа в потоке UI завершается, а LayerTree передается на обработку в Raster поток. При запуске Flutter приложения, инициализируются четыре потока
UI Thread - основная программа пользователя и код фреймворка на Dart Raster Thread - поток работы с OpenGL и отрисовкой на GPU IO Thread - вспомогательный поток для Raster thread, обработка assets Platform Thread - платформозависимый код приложения В методе SkCanvas:Flush производится выполнение операций Skia, из очереди, которая была подготовлена на предыдущем этапе, эти операции выполняют непосредственную отрисовку в OpenGL буфер. После отрисовки вызывается метод платформы SwapBuffer, изображение появляется на экране, а цикл рендеринга фрейма в потоке GPU завершается. По временному графику видно, что время подготовки сцены в UI потоке и её последующей композиции в Raster потоке мало, по сравнению с временем выполнения непосредственной отрисовки в SkCanvas:Flush. В нашем случае простой сцены это в пределах нормы, но при сложной сцене время отрисовки может значительно возрасти. Как правило, это связно с подготовкой программ для графического процессора по каждой из операций Skia. Обратите внимание на пару GrGlProgram в операциях отрисовки прямоугольников из нашего примера - FillRectOp на графике, это как раз место где генерируются и компилируются программы для GPU. Shaders #extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust; in float2 position; in half4 color; noperspective out half4 vcolor_Stage0; void main() { // Primitive Processor QuadPerEdgeAAGeometryProcessor vcolor_Stage0 = color; sk_Position = float4(position.x , position.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require noperspective in half4 vcolor_Stage0; out half4 sk_FragColor; void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, QuadPerEdgeAAGeometryProcessor outputColor_Stage0 = vcolor_Stage0; outputCoverage_Stage0 = half4(1); } { // Xfer Processor: Porter Duff sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0; } } #extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust; in float2 position; in half4 color; noperspective out half4 vcolor_Stage0; void main() { // Primitive Processor QuadPerEdgeAAGeometryProcessor vcolor_Stage0 = color; sk_Position = float4(position.x , position.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require uniform float4 uinnerRect_Stage1_c0; uniform half2 uradiusPlusHalf_Stage1_c0; uniform float4 uinnerRect_Stage1_c0_c0; uniform half2 uradiusPlusHalf_Stage1_c0_c0; noperspective in half4 vcolor_Stage0; out half4 sk_FragColor; half4 CircularRRect_Stage1_c0_c0(half4 _input) { float2 dxy0 = uinnerRect_Stage1_c0_c0.LT - sk_FragCoord.xy; float2 dxy1 = sk_FragCoord.xy - uinnerRect_Stage1_c0_c0.RB; float2 dxy = max(max(dxy0, dxy1), 0.0); half alpha = half(saturate(uradiusPlusHalf_Stage1_c0_c0.x - length(dxy))); alpha = 1.0 - alpha; return _input * alpha; } inline half4 CircularRRect_Stage1_c0(half4 _input) { float2 dxy0 = uinnerRect_Stage1_c0.LT - sk_FragCoord.xy; float2 dxy1 = sk_FragCoord.xy - uinnerRect_Stage1_c0.RB; float2 dxy = max(max(dxy0, dxy1), 0.0); half alpha = half(saturate(uradiusPlusHalf_Stage1_c0.x - length(dxy))); return CircularRRect_Stage1_c0_c0(_input) * alpha; } void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, QuadPerEdgeAAGeometryProcessor outputColor_Stage0 = vcolor_Stage0; outputCoverage_Stage0 = half4(1); } half4 output_Stage1; output_Stage1 = CircularRRect_Stage1_c0(outputCoverage_Stage0); { // Xfer Processor: Porter Duff sk_FragColor = outputColor_Stage0 * output_Stage1; } } шейдеров гораздо больше (с учётом эффекта при нажатии кнопки) Шейдеры классического примера с каунтером #extension GL_NV_shader_noperspective_interpolation: require
uniform float4 sk_RTAdjust; in float2 inPosition; in half4 inColor; in half3 inShadowParams; noperspective out half3 vinShadowParams_Stage0; noperspective out half4 vinColor_Stage0; void main() { // Primitive Processor RRectShadow vinShadowParams_Stage0 = inShadowParams; vinColor_Stage0 = inColor; float2 _tmp_0_inPosition = inPosition; sk_Position = float4(_tmp_0_inPosition.x , _tmp_0_inPosition.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require uniform sampler2D uTextureSampler_0_Stage0; noperspective in half3 vinShadowParams_Stage0; noperspective in half4 vinColor_Stage0; out half4 sk_FragColor; void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, RRectShadow half3 shadowParams; shadowParams = vinShadowParams_Stage0; outputColor_Stage0 = vinColor_Stage0; half d = length(shadowParams.xy); float2 uv = float2(shadowParams.z * (1.0 - d), 0.5); half factor = sample(uTextureSampler_0_Stage0, uv).000r.a; outputCoverage_Stage0 = half4(factor); } { // Xfer Processor: Porter Duff sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0; } } #extension GL_NV_shader_noperspective_interpolation: require uniform float4 sk_RTAdjust; in float2 position; in half4 color; noperspective out half4 vcolor_Stage0; void main() { // Primitive Processor QuadPerEdgeAAGeometryProcessor vcolor_Stage0 = color; sk_Position = float4(position.x , position.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require noperspective in half4 vcolor_Stage0; out half4 sk_FragColor; void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, QuadPerEdgeAAGeometryProcessor outputColor_Stage0 = vcolor_Stage0; outputCoverage_Stage0 = half4(1); } { // Xfer Processor: Porter Duff sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0; } } #extension GL_NV_shader_noperspective_interpolation: require uniform float4 sk_RTAdjust; uniform float3x3 ulocalMatrix_Stage0; in float2 inPosition; in half4 inColor; in float4 inCircleEdge; noperspective out float4 vinCircleEdge_Stage0; noperspective out half4 vinColor_Stage0; void main() { // Primitive Processor CircleGeometryProcessor vinCircleEdge_Stage0 = inCircleEdge; vinColor_Stage0 = inColor; float2 _tmp_0_inPosition = inPosition; float2 _tmp_1_inPosition = (ulocalMatrix_Stage0 * inPosition.xy1).xy; sk_Position = float4(_tmp_0_inPosition.x , _tmp_0_inPosition.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require noperspective in float4 vinCircleEdge_Stage0; noperspective in half4 vinColor_Stage0; out half4 sk_FragColor; void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, CircleGeometryProcessor float4 circleEdge; circleEdge = vinCircleEdge_Stage0; outputColor_Stage0 = vinColor_Stage0; float d = length(circleEdge.xy); half distanceToOuterEdge = half(circleEdge.z * (1.0 - d)); half edgeAlpha = saturate(distanceToOuterEdge); outputCoverage_Stage0 = half4(edgeAlpha); } { // Xfer Processor: Porter Duff sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0; } } #extension GL_NV_shader_noperspective_interpolation: require uniform float4 sk_RTAdjust; in float2 position; in half4 inColor; noperspective out half4 vcolor_Stage0; void main() { // Primitive Processor VerticesGP half4 color = inColor; color = color.bgra; color = color; color = half4(color.rgb * color.a, color.a); vcolor_Stage0 = color; float2 _tmp_0_position = position; sk_Position = float4(_tmp_0_position.x , _tmp_0_position.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require uniform half4 ucolor_Stage1_c0_c0; noperspective in half4 vcolor_Stage0; out half4 sk_FragColor; half4 ConstColorProcessor_Stage1_c0_c0(half4 _input) { return ucolor_Stage1_c0_c0; } half4 BlurredEdgeFragmentProcessor_Stage1_c0_c1(half4 _input) { half inputAlpha = _input.w; half factor = 1.0 - inputAlpha; @switch (0) { case 0: factor = exp((-factor * factor) * 4.0) - 0.017999999225139618; break; case 1: factor = smoothstep(1.0, 0.0, factor); break; } return half4(factor); } inline half4 Blend_Stage1_c0(half4 _input) { // Blend mode: Modulate (SkMode behavior) return blend_modulate(ConstColorProcessor_Stage1_c0_c0(half4(1)), BlurredEdgeFragmentProcessor_Stage1_c0_c1(_input)); } void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, VerticesGP outputColor_Stage0 = vcolor_Stage0; outputCoverage_Stage0 = half4(1); } half4 output_Stage1; output_Stage1 = Blend_Stage1_c0(outputColor_Stage0); { // Xfer Processor: Porter Duff sk_FragColor = output_Stage1 * outputCoverage_Stage0; } } #extension GL_NV_shader_noperspective_interpolation: require uniform float4 sk_RTAdjust; uniform float2 uAtlasSizeInv_Stage0; in float2 inPosition; in half4 inColor; in ushort2 inTextureCoords; noperspective out float2 vTextureCoords_Stage0; noperspective out float vTexIndex_Stage0; noperspective out half4 vinColor_Stage0; void main() { // Primitive Processor Texture int texIdx = 0; float2 unormTexCoords = float2(inTextureCoords.x, inTextureCoords.y); vTextureCoords_Stage0 = unormTexCoords * uAtlasSizeInv_Stage0; vTexIndex_Stage0 = float(texIdx); vinColor_Stage0 = inColor; float2 _tmp_0_inPosition = inPosition; sk_Position = float4(inPosition.x , inPosition.y, 0, 1); } }#extension GL_NV_shader_noperspective_interpolation: require uniform sampler2D uTextureSampler_0_Stage0; noperspective in float2 vTextureCoords_Stage0; noperspective in float vTexIndex_Stage0; noperspective in half4 vinColor_Stage0; out half4 sk_FragColor; void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, Texture outputColor_Stage0 = vinColor_Stage0; half4 texColor; { texColor = sample(uTextureSampler_0_Stage0, vTextureCoords_Stage0).rrrr; } outputCoverage_Stage0 = texColor; } { // Xfer Processor: Porter Duff sk_FragColor = outputColor_Stage0 * outputCoverage_Stage0; } } #extension GL_NV_shader_noperspective_interpolation: require uniform float4 sk_RTAdjust; uniform float4 uviewMatrix_Stage0; in float2 position; in half4 inColor; noperspective out half4 vcolor_Stage0; void main() { // Primitive Processor VerticesGP half4 color = inColor; color = color.bgra; color = color; color = half4(color.rgb * color.a, color.a); vcolor_Stage0 = color; float2 _tmp_0_position = uviewMatrix_Stage0.xz * position + uviewMatrix_Stage0.yw; sk_Position = float4(_tmp_0_position.x , _tmp_0_position.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require uniform half4 ucolor_Stage1_c0_c0; noperspective in half4 vcolor_Stage0; out half4 sk_FragColor; half4 ConstColorProcessor_Stage1_c0_c0(half4 _input) { return ucolor_Stage1_c0_c0; } half4 BlurredEdgeFragmentProcessor_Stage1_c0_c1(half4 _input) { half inputAlpha = _input.w; half factor = 1.0 - inputAlpha; @switch (0) { case 0: factor = exp((-factor * factor) * 4.0) - 0.017999999225139618; break; case 1: factor = smoothstep(1.0, 0.0, factor); break; } return half4(factor); } inline half4 Blend_Stage1_c0(half4 _input) { // Blend mode: Modulate (SkMode behavior) return blend_modulate(ConstColorProcessor_Stage1_c0_c0(half4(1)), BlurredEdgeFragmentProcessor_Stage1_c0_c1(_input)); } void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, VerticesGP outputColor_Stage0 = vcolor_Stage0; outputCoverage_Stage0 = half4(1); } half4 output_Stage1; output_Stage1 = Blend_Stage1_c0(outputColor_Stage0); { // Xfer Processor: Porter Duff sk_FragColor = output_Stage1 * outputCoverage_Stage0; } } #extension GL_NV_shader_noperspective_interpolation: require uniform float4 sk_RTAdjust; uniform float3x3 ulocalMatrix_Stage0; in float2 inPosition; in half4 inColor; in float4 inCircleEdge; noperspective out float4 vinCircleEdge_Stage0; noperspective out half4 vinColor_Stage0; void main() { // Primitive Processor CircleGeometryProcessor vinCircleEdge_Stage0 = inCircleEdge; vinColor_Stage0 = inColor; float2 _tmp_0_inPosition = inPosition; float2 _tmp_1_inPosition = (ulocalMatrix_Stage0 * inPosition.xy1).xy; sk_Position = float4(_tmp_0_inPosition.x , _tmp_0_inPosition.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require uniform float3x3 umatrix_Stage1_c0_c0_c0; uniform sampler2D uTextureSampler_0_Stage1; noperspective in float4 vinCircleEdge_Stage0; noperspective in half4 vinColor_Stage0; out half4 sk_FragColor; half4 TextureEffect_Stage1_c0_c0_c0_c0(half4 _input, float2 _coords) { return sample(uTextureSampler_0_Stage1, _coords).000r; } half4 MatrixEffect_Stage1_c0_c0_c0(half4 _input, float2 _coords) { return TextureEffect_Stage1_c0_c0_c0_c0(_input, ((umatrix_Stage1_c0_c0_c0) * _coords.xy1).xy); } half4 DeviceSpaceEffect_Stage1_c0_c0(half4 _input) { return MatrixEffect_Stage1_c0_c0_c0(_input, sk_FragCoord.xy); } inline half4 Blend_Stage1_c0(half4 _input) { // Blend mode: DstIn (Compose-One behavior) return blend_dst_in(DeviceSpaceEffect_Stage1_c0_c0(half4(1)), _input); } void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, CircleGeometryProcessor float4 circleEdge; circleEdge = vinCircleEdge_Stage0; outputColor_Stage0 = vinColor_Stage0; float d = length(circleEdge.xy); half distanceToOuterEdge = half(circleEdge.z * (1.0 - d)); half edgeAlpha = saturate(distanceToOuterEdge); outputCoverage_Stage0 = half4(edgeAlpha); } half4 output_Stage1; output_Stage1 = Blend_Stage1_c0(outputCoverage_Stage0); { // Xfer Processor: Porter Duff sk_FragColor = outputColor_Stage0 * output_Stage1; } } #extension GL_NV_shader_noperspective_interpolation: require uniform float4 sk_RTAdjust; in float4 radii_selector; in float4 corner_and_radius_outsets; in float4 aa_bloat_and_coverage; in float4 skew; in float2 translate; in float4 radii_x; in float4 radii_y; in half4 color; noperspective out half4 vcolor_Stage0; noperspective out float2 varccoord_Stage0; void main() { // Primitive Processor GrFillRRectOp::Processor vcolor_Stage0 = color; float2 corner = corner_and_radius_outsets.xy; float2 radius_outset = corner_and_radius_outsets.zw; float2 aa_bloat_direction = aa_bloat_and_coverage.xy; float coverage = aa_bloat_and_coverage.z; float is_linear_coverage = aa_bloat_and_coverage.w; float2 pixellength = inversesqrt(float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw))); float4 normalized_axis_dirs = skew * pixellength.xyxy; float2 axiswidths = (abs(normalized_axis_dirs.xy) + abs(normalized_axis_dirs.zw)); float2 aa_bloatradius = axiswidths * pixellength * .5; float4 radii_and_neighbors = radii_selector* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx); float2 radii = radii_and_neighbors.xy; float2 neighbor_radii = radii_and_neighbors.zw; if (any(greaterThan(aa_bloatradius, float2(1)))) { corner = max(abs(corner), aa_bloatradius) * sign(corner); coverage /= max(aa_bloatradius.x, 1) * max(aa_bloatradius.y, 1); radii = float2(0); } if (any(lessThan(radii, aa_bloatradius * 1.25))) { radii = aa_bloatradius; radius_outset = floor(abs(radius_outset)) * radius_outset; is_linear_coverage = 1; } else { radii = clamp(radii, pixellength, 2 - pixellength); neighbor_radii = clamp(neighbor_radii, pixellength, 2 - pixellength); float2 spacing = 2 - radii - neighbor_radii; float2 extra_pad = max(pixellength * .0625 - spacing, float2(0)); radii -= extra_pad * .5; } float2 aa_outset = aa_bloat_direction.xy * aa_bloatradius; float2 vertexpos = corner + radius_outset * radii + aa_outset; float2x2 skewmatrix = float2x2(skew.xy, skew.zw); float2 devcoord = vertexpos * skewmatrix + translate; if (0 != is_linear_coverage) { varccoord_Stage0.xy = float2(0, coverage); } else { float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner; varccoord_Stage0.xy = float2(arccoord.x+1, arccoord.y); } sk_Position = float4(devcoord.x , devcoord.y, 0, 1); } #extension GL_NV_shader_noperspective_interpolation: require uniform float3x3 umatrix_Stage1_c0_c0_c0; uniform sampler2D uTextureSampler_0_Stage1; noperspective in half4 vcolor_Stage0; noperspective in float2 varccoord_Stage0; out half4 sk_FragColor; half4 TextureEffect_Stage1_c0_c0_c0_c0(half4 _input, float2 _coords) { return sample(uTextureSampler_0_Stage1, _coords).000r; } half4 MatrixEffect_Stage1_c0_c0_c0(half4 _input, float2 _coords) { return TextureEffect_Stage1_c0_c0_c0_c0(_input, ((umatrix_Stage1_c0_c0_c0) * _coords.xy1).xy); } half4 DeviceSpaceEffect_Stage1_c0_c0(half4 _input) { return MatrixEffect_Stage1_c0_c0_c0(_input, sk_FragCoord.xy); } inline half4 Blend_Stage1_c0(half4 _input) { // Blend mode: DstIn (Compose-One behavior) return blend_dst_in(DeviceSpaceEffect_Stage1_c0_c0(half4(1)), _input); } void main() { half4 outputColor_Stage0; half4 outputCoverage_Stage0; { // Stage 0, GrFillRRectOp::Processor outputColor_Stage0 = vcolor_Stage0; float x_plus_1=varccoord_Stage0.x, y=varccoord_Stage0.y; half coverage; if (0 == x_plus_1) { coverage = half(y); } else { float fn = x_plus_1 * (x_plus_1 - 2); fn = fma(y,y, fn); float fnwidth = fwidth(fn); half d = half(fn/fnwidth); coverage = clamp(.5 - d, 0, 1); } outputCoverage_Stage0 = half4(coverage); } half4 output_Stage1; output_Stage1 = Blend_Stage1_c0(outputCoverage_Stage0); { // Xfer Processor: Porter Duff sk_FragColor = outputColor_Stage0 * output_Stage1; } } Можно ли предварительно скомпилировать все возможные шейдеры, перед запуском программы?
Inside Flutter Skia =========== Источник: habr.com =========== Похожие новости:
Блог компании Лига Ставок ), #_dart, #_flutter |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 01:05
Часовой пояс: UTC + 5