Используйте флаттерные шейдеры для создания программного обеспечения для пиксельной живописи для улучшения производительности. Испытывайте проблемы с памятью.

Вопрос или проблема

Я пишу программное обеспечение для рисования с использованием 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, выглядит эффективно, однако есть несколько моментов, которые могут быть оптимизированы.

  1. Переиспользование объектов: Попробуйте использовать пул объектов для хранения и переиспользования ui.Image, вместо создания нового изображения каждый раз. Это поможет снизить нагрузку на сборщик мусора.
  2. Управление состоянием с помощью isInvalidated: Режим «не валидирован» может привести к ненужным вызовам decodeImageFromPixels. Подумайте о том, чтобы этот механизм использовался более избирательно — например, проверяйте изменения не для каждого слоя, а для маски измененных областей.

Проблемы с памятью: причины и решения

Использование шейдеров в приложениях может повысить производительность, но риски утечек памяти или чрезмерного использования ресурсов также увеличиваются. Рассмотрим несколько стратегий для предотвращения этих проблем:

  1. Оптимизация кодирования шейдеров: Ваш 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);
    }
  2. Снижение частоты обновления изображений: Если ваше приложение обновляет изображения слишком часто, это может вызвать значительные проблемы с производительностью. Установите ограничение на частоту обновления, а также добавьте возможность отложенного перерисовки.

  3. Использование Future и Completer: Вместо синхронного определения изображения, попробуйте использовать асинхронные вызовы и управлять очередями на наложение. Это снизит нагрузку и поможет избежать блокировки интерфейса.

Заключение

Ваш подход к созданию программного обеспечения для рисования на Flutter с использованием шейдеров — это шаг в правильном направлении. Однако важно оптимизировать как код на уровне шейдеров, так и логику управления состоянием. Устранение утечек памяти и оптимизация сборки элементов поможет создать стабильное и высокопроизводительное приложение.

Применяя предложенные стратегии, вы сможете не только улучшить производительность, но и уменьшить потребление памяти, что, в свою очередь, создаст лучший пользовательский опыт для ваших клиентов.

Работа в сфере программирования — это постоянный процесс обучения и улучшения. Читайте, экспериментируйте и адаптируйте свои решения, чтобы достичь своих целей и сделать ваше ПО тем, о чем вы мечтаете.

Оцените материал
Добавить комментарий

Капча загружается...