Вопрос или проблема
Я пишу программное обеспечение для рисования с использованием Flutter, и чтобы улучшить производительность программного обеспечения и добавить такие функции, как кисти, я использую шейдеры для рисования. То есть я устанавливаю изображение в шейдер перед рисованием, я наблюдал, что это работает достаточно хорошо, а после рисования используется toImageSync() для конвертации в объект ui.Image для отображения на экране.
Мой текущий подход:
В настоящее время у меня есть класс Layer, который содержит такие свойства, как режим смешивания, скрытие слоев и, что наиболее важно, переменная типа Uint8List, которая хранит информацию о слоях. Предполагая, что размер моего холста составляет 16 x 16, информация, хранящаяся в переменной Uint8List, составляет 16 x 16 x 4. Также есть переменная типа ui.Image, которая используется при смешивании нескольких слоев. ui.Image будет обновлено только в том случае, если переменная Unint8List изменена. Это мой текущий подход.
//isInvalidated Функция переменной заключается в том, чтобы помечать, были ли изменены данные текущего слоя. Если они были изменены, они генерируются при выполнении ui.decodeImageFromPixels().
//image Изображение для смешивания слоев
Future<ui.Image?> render(ui.Size canvasSize) async {
if (!isInvalidated) {
return image;
}
isInvalidated = false;
if (!isCanRender) {
return null;
}
var complater = Completer<ui.Image>();
ui.decodeImageFromPixels(pixelData, canvasSize.width.toInt(), canvasSize.height.toInt(), ui.PixelFormat.rgba8888, complater.complete);
image = await complater.future;
return image;
}
//Это функция, используемая для смешивания слоев
Future<ui.Picture> blendLayers(BuildContext context, Size canvasSize) async {
ui.PictureRecorder recorder = ui.PictureRecorder();
ui.Canvas canvas = ui.Canvas(recorder);
var frameModel = context.read<FrameModel>();
for (int i = 0; i < frameModel.frameList[frameModel.currentEditFrame].layers.length; i++) {
if (!frameModel.frameList[frameModel.currentEditFrame].layers[i].visible) {
continue;
}
final image = await frameModel.frameList[frameModel.currentEditFrame].layers[i].render(context.read<ToolParameter>().canvasSize);
var layerBlendMode = frameModel.frameList[frameModel.currentEditFrame].layers[i].blendMode;
if (i == 0) {
layerBlendMode = LayerBlendMode.normal;
}
if (image != null) {
canvas.drawImage(
image,
Offset.zero,
Paint()
..color = Color.fromRGBO(255, 255, 255, frameModel.frameList[frameModel.currentEditFrame].layers[i].transparence / 100.0)
..blendMode = getBlendMode(layerBlendMode));
}
}
return recorder.endRecording();
}
Этот метод обеспечил хорошую производительность, но мне кажется, что этого недостаточно, и я пытаюсь найти новые решения, чтобы предоставить своим пользователям лучшее программное обеспечение, что также является моей мечтой.
Я пробовал:
У меня не было много опыта с шейдерами ранее, но чтобы улучшить производительность, я попробовал их, и должен сказать, что они действительно обеспечили хорошую производительность, но через некоторое время возникали проблемы с памятью, и в итоге программа аварийно завершалась.
#version 460 core
#include <flutter/runtime_effect.glsl>
precision mediump float;
//Цвет на выходе
out vec4 fragColor;
//Центральная позиция и радиус
uniform vec2 u_center;
uniform float u_radius;
//цвет кисти
uniform vec4 u_color;
//тип фигуры
uniform float u_shape_type;
//Текстура действительна, если выбранный тип - Пользовательская
//uniform sampler2D uTexture;
//симметрия по оси x
uniform float x_axis_symmetry;
//позиция симметрии по оси x
//uniform float x_axis_symmetry_position;
//симметрия по оси y
//uniform float y_axis_symmetry;
//позиция симметрии по оси y
//uniform float y_axis_symmetry_position;
void main()
{
if(u_shape_type == 0){
//нарисовать прямоугольник
if(FlutterFragCoord().xy.x > u_center[0] - u_radius &&
FlutterFragCoord().xy.x < u_center[0] + u_radius &&
FlutterFragCoord().xy.y > u_center[1] - u_radius &&
FlutterFragCoord().xy.y < u_center[1] + u_radius)
{
fragColor = u_color;
}else{
fragColor = vec4(0.0);
}
}else if(u_shape_type == 1){
//Вычисляем расстояние от текущей координаты до центра круга
float dis = distance(FlutterFragCoord().xy, u_center);
//Если расстояние меньше радиуса, цвет рисуется, в противном случае - прозрачный
if (dis < u_radius)
{
fragColor = u_color;
}
else
{
fragColor = vec4(0.0);
}
}else if(u_shape_type == 2){
//графика
//fragColor = texture(u_texture, FlutterFragCoord().xy / u_resolution);
}
}
Это мой код шейдера
ui.PictureRecorder recorder = ui.PictureRecorder();
ui.Canvas canvas = ui.Canvas(recorder);
//Создайте шейдер и задайте параметры шейдера
//код ...
//canvas.draw()...
//код ...
ui.Image image = recorder.endRecording().toImageSync(canvas_width, canvas_height);
Затем используйте изображение для смешивания слоев
Я ожидаю результаты :
Согласно моим наблюдениям, производительность действительно улучшилась, но память программы работала не гладко.
Что я ожидаю, так это чтобы память программы работала гладко, в то время как производительность могла бы быть улучшена.
Ответ или решение
Создание программного обеспечения для рисования с использованием шейдеров Flutter для повышения производительности и снижения проблем с памятью
Разработка программного обеспечения для рисования — это творческий и технически сложный процесс, особенно когда речь идет о повышении производительности и эффективном управлении памятью. Если вы используете Flutter для создания такого ПО, то применение шейдеров может дать вам необходимые преимущества в производительности. Однако, как вы уже заметили, с ними могут возникнуть проблемы с памятью. В данной статье мы подробно рассмотрим текущие подходы и предложим оптимизации для улучшения стабильности и производительности вашего приложения.
Текущий подход: анализ и критика
Вы уже внедрили слой, который включает в себя переменные, такие как Uint8List
, для хранения информации о слоях, а также ui.Image
для смешивания этих слоев. Ваша реализация метода render
, отвечающего за преобразование Uint8List
в ui.Image
, выглядит эффективно, однако есть несколько моментов, которые могут быть оптимизированы.
- Переиспользование объектов: Попробуйте использовать пул объектов для хранения и переиспользования
ui.Image
, вместо создания нового изображения каждый раз. Это поможет снизить нагрузку на сборщик мусора. - Управление состоянием с помощью
isInvalidated
: Режим «не валидирован» может привести к ненужным вызовамdecodeImageFromPixels
. Подумайте о том, чтобы этот механизм использовался более избирательно — например, проверяйте изменения не для каждого слоя, а для маски измененных областей.
Проблемы с памятью: причины и решения
Использование шейдеров в приложениях может повысить производительность, но риски утечек памяти или чрезмерного использования ресурсов также увеличиваются. Рассмотрим несколько стратегий для предотвращения этих проблем:
-
Оптимизация кодирования шейдеров: Ваш GLSL код уже правильно организован, однако рекомендуется пересмотреть его для учета возможных ошибок и оптимизаций. Например, избегайте создания временных переменных, если они не необходимы.
Пример оптимизированного кода:
void main() { vec2 coord = FlutterFragCoord().xy; bool inShape = false; if(u_shape_type == 1) { // Circle inShape = distance(coord, u_center) < u_radius; } else if(u_shape_type == 0) { // Rectangle inShape = coord.x > u_center.x - u_radius && coord.x < u_center.x + u_radius && coord.y > u_center.y - u_radius && coord.y < u_center.y + u_radius; } fragColor = inShape ? u_color : vec4(0.0); }
-
Снижение частоты обновления изображений: Если ваше приложение обновляет изображения слишком часто, это может вызвать значительные проблемы с производительностью. Установите ограничение на частоту обновления, а также добавьте возможность отложенного перерисовки.
-
Использование
Future
иCompleter
: Вместо синхронного определения изображения, попробуйте использовать асинхронные вызовы и управлять очередями на наложение. Это снизит нагрузку и поможет избежать блокировки интерфейса.
Заключение
Ваш подход к созданию программного обеспечения для рисования на Flutter с использованием шейдеров — это шаг в правильном направлении. Однако важно оптимизировать как код на уровне шейдеров, так и логику управления состоянием. Устранение утечек памяти и оптимизация сборки элементов поможет создать стабильное и высокопроизводительное приложение.
Применяя предложенные стратегии, вы сможете не только улучшить производительность, но и уменьшить потребление памяти, что, в свою очередь, создаст лучший пользовательский опыт для ваших клиентов.
Работа в сфере программирования — это постоянный процесс обучения и улучшения. Читайте, экспериментируйте и адаптируйте свои решения, чтобы достичь своих целей и сделать ваше ПО тем, о чем вы мечтаете.