Android-приложения в Lazarus

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Alex2013
долгожитель
Сообщения: 3323
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

На Snapdragon-е (Quest-3s) красивее .
Изображение
https://youtu.be/hdOmqhATKIY
(Это уже тест на реальном устройстве под управлением ОС Андроид )
Неплохо, но скорость все равно не очень радует, так что нужно будет пробовать сделать "OpenGL версию".
Seenkao
энтузиаст
Сообщения: 587
Зарегистрирован: 01.04.2020 02:37:12
Контактная информация:

Сообщение Seenkao »

Alex2013 писал(а): 03.07.2026 05:02:17 Исправленные версии процедур и функций ...
можешь дальше исправлять и получить выхлоп в этих местах в два а то и более раза.
Почитать можешь здесь, при чём всю тему. Полезное пишут.
Alex2013
долгожитель
Сообщения: 3323
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Seenkao писал(а): 05.07.2026 14:41:49
Alex2013 писал(а): 03.07.2026 05:02:17 Исправленные версии процедур и функций ...
можешь дальше исправлять и получить выхлоп в этих местах в два а то и более раза.
Почитать можешь здесь, при чём всю тему. Полезное пишут.
1. Разумеется, я прекрасно понимаю, что причина выравнивания в оптимизации, но текущий код я пока вообще не оптимизировал (возможно, об этом позаботились его изначальные авторы (основу брал в примере из Hiasm, хотя, возможно, что это была далеко не первая его реализация), но сейчас я особо снова в этот код не вникал), к тому же вычисления с плавающей точкой требуют оптимизаций, отличных от целочисленных ухищрений.
2. Основной текущей задачей было, наоборот, избавиться от оптимизации и, получив чистый массив данных, загрузить его как шаблон «фоновой текстуры» в как бы по идее универсальный «волновой процессор».
3. Немного сомневаюсь, что в случае зоопарка реализаций ARM-платформ в принципе возможна стабильная «железячная оптимизация».
Зы.
За ссылку, разумеется, спасибо! Начал читать (с ходу такой объем информации не переварить, но попробую разобраться). :idea:
Зы. Зы.
Цель текущего проекта — самостоятельно воспроизвести на мобильной платформе что-то, что значительно отличается от стандартных примеров, и то, что может стать для меня неким «розеттским камнем» в деле переноса наработок из «мира виндовс» в «мир мобильных платформ».
(Вначале думал просто переписать, например, какой-нибудь «Терис с Дельфи» (или, например, всё тот же многострадальный «Дум»), но быстро понял, переписать что-то подобное будет, с одной стороны, черезчур простой задачей, а с другой — есть шанс погрязнуть в тонкостях реализации конкретной игры, не имеющих отношения к освоению программирования под андроид.) Так что пока как-то так.

Следующий этап развития проекта — в использовании «живого фона», получаемого с камеры мобильного устройства.

Зы Зы Зы
Как в LAMW правильно собирать "релизный APK" ?( все скрипты из примеров перепробовал работает только сборка "дебаг" версий APK, а они одна сплошная "дыра в системе" (требуют всех разрешений ОС держат открытыми проты отладки и т.п. )
Seenkao
энтузиаст
Сообщения: 587
Зарегистрирован: 01.04.2020 02:37:12
Контактная информация:

Сообщение Seenkao »

Tools -> [LAMW] Android Module Wizard -> Build Released Signed...

А вообще, создавая новый проект, он вроде без отладки идёт.
Alex2013
долгожитель
Сообщения: 3323
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Seenkao писал(а): 05.07.2026 21:02:10 Tools -> [LAMW] Android Module Wizard -> Build Released Signed...

А вообще, создавая новый проект, он вроде без отладки идёт.
У меня работает только laz4android 2.0.12...
...создавая новый проект, он вроде без отладки идёт.
Идет то может быть без отладки но apk собирает только такие...
ХХХХХ-debug.apk и ХХХХХ-debug-unaligned.apk
Tools -> [LAMW] Android Module Wizard -> Build Released Signed...
Упс! Действительно есть такое. Спасибо! Попробую
Изображение
Seenkao
энтузиаст
Сообщения: 587
Зарегистрирован: 01.04.2020 02:37:12
Контактная информация:

Сообщение Seenkao »

Alex2013 писал(а): 05.07.2026 21:20:41 У меня работает только laz4android 2.0.12...
Я уже кидал тебе ссылку. Дополнительно в файле androidwidget найди строчку (функцию) procedure TAndroidWidget.SetBounds(NewLeft, NewTop, NewWidth, NewHeight: integer); и замени её код:

Код: Выделить всё

procedure TAndroidWidget.SetBounds(NewLeft, NewTop, NewWidth, NewHeight: integer);
begin
     // 18/06/2026: Lazarus 4.x up bug fix
	 // In Lazarus 4.0 and above, if we add a component by double clicking, 
	 // it doesn't appear because it has a width=0, height=0 size.	 
     if (csDesigning in ComponentState) then
     begin
          if NewWidth=0 then NewWidth:=100;
          if NewHeight=0 then NewHeight:=40;
     end; 
  if (Left=NewLeft) and (Top=NewTop) and (Width=NewWidth) and (Height=NewHeight) then Exit;
  FLeft:=NewLeft;
  FTop:=NewTop;
  FWidth:=NewWidth;
  FHeight:=NewHeight;
  if (csDesigning in ComponentState) then
    if Assigned(Parent) then
      Parent.Invalidate
end;
Alex2013
долгожитель
Сообщения: 3323
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Seenkao писал(а): 05.07.2026 21:32:02 Я уже кидал тебе ссылку. Дополнительно в файле androidwidget найди строчку (функцию) procedure TAndroidWidget.SetBounds(NewLeft, NewTop, NewWidth, NewHeight: integer); и замени её код:
Спасибо я попробовал но что-то у меня пошло сильно не так ( пришлось откатываться к архивной копии впрочем возможно это жара так повлияла ( пару раз успел заметить заметил что монитор ресурсов показывал резкий разгон и скачек температуры встроенной видеокарты (читай ядра CPU) выше 100 градусов после чего система разумеется моментально влетала в перегрузку ( похоже эмулятор кривой))
Последний раз редактировалось Alex2013 12.07.2026 15:59:57, всего редактировалось 1 раз.
Alex2013
долгожитель
Сообщения: 3323
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Запустил программу на реальном смартфоне...
1. Нужно обязательно отключать все лишение «требования разрешений» (иначе антивирус входит «в режим паники»).
2. «Сборка релиза» не работает и портит проект (APK вообще перестает собираться даже в дебуг-версии (спасает только восстановление всего проекта из бэкапа, восстановление одних лазарус-исходников проекта ничем не поможет)).
3. В интерфейсе моей программы загадочным образом пропали все кнопки (тут это некритично, можно использовать клик по картинке, но что я сделал не так — непонятно (в эмуляторе и на QUEST3S всё Ок)).
4. Огромный заголовок окна определенно действует на нервы (как убрать — не нашел, стандартные LCL-приемы не помогают).
5. Тормозит почти так же, как на эмуляторе.
Зы
https://youtu.be/RpKPGT-y9Fc
Зы Зы
(фокус с procedure TAndroidWidget.SetBounds непременно попробую, спасибо и извиняюсь за отпечатки! )
Seenkao
энтузиаст
Сообщения: 587
Зарегистрирован: 01.04.2020 02:37:12
Контактная информация:

Сообщение Seenkao »

Если ты про LAMW, то версия сама по себе глючная. Там в настройках, например, кнопок надо включать всякие свойства, чтоб они правильно отображались. Это что-то вроде свойства wrapcontent. Чтоб это понять, надо смотреть в java как это всё сделано. Ведь именно посредством java всё реализовано, а не посредством FPC/Lazarus.
Seenkao
энтузиаст
Сообщения: 587
Зарегистрирован: 01.04.2020 02:37:12
Контактная информация:

Сообщение Seenkao »

Alex2013 писал(а): 12.07.2026 13:22:00 5. Тормозит почти так же, как на эмуляторе.
код есть? Может по ZenGL попробую запустить и посмотреть результат?
Alex2013
долгожитель
Сообщения: 3323
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

1 "LAMW, то версия сама по себе глючная" совершенно согласен но это пока единственная для программирования под ОС Андроид с которой я хоть как-то поладил ( сборка по методу предложенному в ZenGL явно перспективнее но у меня просто не хватило терпения пройти все шаги правильно а поскольку я пока в реалиях андроида практический "не в зуб ногой" разбираться приходится с тем что хоть как-то работает)

2 Что касается исходного кода то его не жалко просто смысла публиковать его нет. Основной модуль "рендера волн" сейчас почти универсальный так что если есть желание повозиться и перенести на свою платформу то я бы посоветовал начать с простой и понятной "тестовой виндовс адаптации " (главное там костылей заметно поменьше будет хотя код все равно явно тестовый и слегка "неухоженный" ) . WinWaveDemo2.7z ( "волновой процессор"(модуль WaveEffectsBin.pas) в этой версии уже "заточен под андроид" но отлаживать его проще вначале было под виндовс (но думаю что при желании можно и под линукс( если винды под рукой нет), без особых проблемы собрать) )
Версия исправленная и конкретно ее (в виде "релиза" с кодом ) я вроде не выкладывал
У меня работает так.
https://youtu.be/oGD4FpbRmn8

Всякий пожарный "боевая" версия WaveEffectsBin (на прямую из текущей андроид версии )
( если что-то и менял от исключительно по мелочи, но пусть будет... "береженого Ада Лавлейс бережет" :wink: )

Код: Выделить всё

unit WaveEffectsBin;

{$mode delphi}
{$POINTERMATH ON}

interface

uses
  Classes, SysUtils,Graphics, Math;

type
  // Структура для хранения состояния волн
  TWaveState = record
    Height: Single;
    Speed: Single;
  end;
  TWaveStateArray= array of TWaveState;

// ☆☆☆ УНИВЕРСАЛЬНАЯ ПРОЦЕДУРА РАБОТЫ С МАССИВОМ ПИКСЕЛЕЙ ☆☆☆
procedure WaveProcessorFXBin(
  Pixels: PByte;              // указатель на данные пикселей (RGBA)
  Width, Height: Integer;     // размеры изображения
  var Waves:TWaveStateArray;  // массив волн
  GridSize: Integer;          // размер сетки
  X, Y: Integer;              // -1 = только симуляция
  Radius: Integer;            // 0 = только симуляция
  HeightWave: Single;         // 0 = только симуляция
  Viscosity, WavesSpeed, LightIntensity, Depth: Single;
  Background: PByte = nil;    // указатель на фон (nil = градиент)
  BgWidth: Integer = 0; BgHeight: Integer = 0  // размеры фона
);

// Вспомогательные функции
procedure InitWaves(var Waves: TWaveStateArray; GridSize: Integer);
procedure ClearWaves(var Waves:TWaveStateArray; GridSize: Integer);
procedure GenerateGradient(Pixels: PByte; Width, Height: Integer);
procedure InitWavesRandom(var Waves: TWaveStateArray; GridSize: Integer; Amplitude: Single);

implementation

// ☆☆☆ ИНИЦИАЛИЗАЦИЯ ВОЛН (СЛУЧАЙНЫЕ ВОЗМУЩЕНИЯ) ☆☆☆
procedure InitWavesRandom(var Waves: TWaveStateArray; GridSize: Integer; Amplitude: Single);
var
  i: Integer;
begin
  for i := 0 to GridSize * GridSize - 1 do
  begin
    // Случайные возмущения с плавным переходом на границах
    Waves[i].Height := (Random - 0.5) * Amplitude * 2;
    Waves[i].Speed := (Random - 0.5) * Amplitude * 0.1;
  end;

  // Границы затухают
  for i := 0 to GridSize - 1 do
  begin
    Waves[i].Height := Waves[i].Height * 0.5;
    Waves[i].Speed := Waves[i].Speed * 0.5;

    Waves[(GridSize - 1) * GridSize + i].Height :=
      Waves[(GridSize - 1) * GridSize + i].Height * 0.5;
    Waves[(GridSize - 1) * GridSize + i].Speed :=
      Waves[(GridSize - 1) * GridSize + i].Speed * 0.5;

    Waves[i * GridSize].Height := Waves[i * GridSize].Height * 0.5;
    Waves[i * GridSize].Speed := Waves[i * GridSize].Speed * 0.5;

    Waves[i * GridSize + GridSize - 1].Height :=
      Waves[i * GridSize + GridSize - 1].Height * 0.5;
    Waves[i * GridSize + GridSize - 1].Speed :=
      Waves[i * GridSize + GridSize - 1].Speed * 0.5;
  end;
end;

// ☆☆☆ СОЗДАНИЕ ГРАДИЕНТА ☆☆☆
procedure GenerateGradient(Pixels: PByte; Width, Height: Integer);
var
  X, Y: Integer;
  idx: Integer;
  R, G, B: Byte;
  RowPtr: PByte;
begin
  if Pixels = nil then Exit;
  
  for Y := 0 to Height - 1 do
  begin
    RowPtr := Pixels + Y * Width * 4;
    for X := 0 to Width - 1 do
    begin
      idx := X * 4;
      
      B := Round(30 + 40 * (Y / Height));
      G := Round(80 + 100 * (Y / Height));
      R := Round(180 + 55 * (1 - (Y / Height)));
      
      RowPtr[idx] := 255;      // Alpha
      RowPtr[idx + 1] := R;    // Red
      RowPtr[idx + 2] := G;    // Green
      RowPtr[idx + 3] := B;    // Blue
    end;
  end;
end;

// ☆☆☆ ИНИЦИАЛИЗАЦИЯ ВОЛН ☆☆☆
procedure InitWaves(var Waves: TWaveStateArray; GridSize: Integer);
var
  i: Integer;
begin
  for i := 0 to GridSize * GridSize - 1 do
  begin
    Waves[i].Height := 0;
    Waves[i].Speed := 1;
  end;
end;

procedure ClearWaves(var Waves:TWaveStateArray; GridSize: Integer);
begin
  InitWaves(Waves, GridSize);
end;

// ☆☆☆ СОЗДАНИЕ ВОЛНЫ ☆☆☆
procedure MakeRipple(var Waves: array of TWaveState; GridSize: Integer;
  CX, CY, Radius: Integer; HeightWave: Single);
var
  X, Y, DX, DY, Dist, RadiusSq: Integer;
  Profile: Single;
  idx: Integer;
begin
  if GridSize < 1 then Exit;
  if (CX < 0) or (CY < 0) or (CX >= GridSize) or (CY >= GridSize) then Exit;
  
  RadiusSq := Radius * Radius;
  
  for X := Max(0, CX - Radius) to Min(GridSize - 1, CX + Radius) do
  begin
    DX := X - CX;
    for Y := Max(0, CY - Radius) to Min(GridSize - 1, CY + Radius) do
    begin
      DY := Y - CY;
      Dist := DX * DX + DY * DY;
      if Dist <= RadiusSq then
      begin
        Profile := 1.0 - Sqrt(Dist) / Radius;
        Profile := Profile * Profile * (3.0 - 2.0 * Profile);
        idx := Y * GridSize + X;
        Waves[idx].Height := Waves[idx].Height + HeightWave * Profile;
      end;
    end;
  end;
end;

// ☆☆☆ СИМУЛЯЦИЯ ВОЛН ☆☆☆
procedure SimulateWaves(var Waves: array of TWaveState; GridSize: Integer;
  Viscosity, WavesSpeed: Single);
var
  X, Y, idx: Integer;
  Ddx, Ddy, Viscosity1: Single;
begin
  if GridSize < 3 then Exit;
  if Length(Waves) < GridSize * GridSize then Exit;
  
  Viscosity1 := 1.0 - Viscosity;
  
  // Обновляем скорости
  for Y := 1 to GridSize - 2 do
    for X := 1 to GridSize - 2 do
    begin
      idx := Y * GridSize + X;
      Ddx := (Waves[idx + 1].Height - Waves[idx].Height) -
             (Waves[idx].Height - Waves[idx - 1].Height);
      Ddy := (Waves[idx + GridSize].Height - Waves[idx].Height) -
             (Waves[idx].Height - Waves[idx - GridSize].Height);
      Waves[idx].Speed := Waves[idx].Speed + (Ddx + Ddy) / WavesSpeed;
    end;
  
  // Обновляем высоты
  for Y := 1 to GridSize - 2 do
    for X := 1 to GridSize - 2 do
    begin
      idx := Y * GridSize + X;
      Waves[idx].Height := (Waves[idx].Height + Waves[idx].Speed) * Viscosity1;
    end;
  
  // Границы (затухание)
  for X := 1 to GridSize - 2 do
  begin
    Waves[X].Height := Waves[GridSize + X].Height * 0.95;
    Waves[(GridSize - 1) * GridSize + X].Height := 
      Waves[(GridSize - 2) * GridSize + X].Height * 0.95;
  end;
  
  for Y := 1 to GridSize - 2 do
  begin
    Waves[Y * GridSize].Height := Waves[Y * GridSize + 1].Height * 0.95;
    Waves[Y * GridSize + GridSize - 1].Height := 
      Waves[Y * GridSize + GridSize - 2].Height * 0.95;
  end;
end;

// ☆☆☆ РЕНДЕРИНГ ВОЛН В МАССИВ ПИКСЕЛЕЙ ☆☆☆
procedure RenderWavesToPixels(
  var Waves: array of TWaveState;
  GridSize: Integer;
  Pixels: PByte;
  Width, Height: Integer;
  Background: PByte;
  BgWidth, BgHeight: Integer;
  LightIntensity, Depth: Single);
var
  X, Y: Integer;
  GridX, GridY: Integer;
  Dx, Dy, HeightFactor, WaveHeight: Single;
  Light: Integer;
  XMap, YMap: Integer;
  R, G, B: Integer;
  idx, bgIdx: Integer;
  RowPtr, BgRowPtr: PByte;
  BgX, BgY: Integer;
begin
  if Pixels = nil then Exit;
  
  for Y := 0 to Height - 1 do
  begin
    RowPtr := Pixels + Y * Width * 4;
    
    for X := 0 to Width - 1 do
    begin
      // Преобразуем координаты пикселя в координаты сетки
      GridX := (X * GridSize) div Width;
      GridY := (Y * GridSize) div Height;
      
      if GridX >= GridSize then GridX := GridSize - 1;
      if GridY >= GridSize then GridY := GridSize - 1;
      
      idx := GridY * GridSize + GridX;
      WaveHeight := Waves[idx].Height;
      
      // Вычисляем наклон
      if GridX + 1 < GridSize then
        Dx := Waves[GridY * GridSize + GridX + 1].Height - WaveHeight
      else
        Dx := 0;
        
      if GridY + 1 < GridSize then
        Dy := Waves[(GridY + 1) * GridSize + GridX].Height - WaveHeight
      else
        Dy := 0;
      
      // Фактор глубины (искажение)
      HeightFactor := WaveHeight + Depth;
      if HeightFactor < 1.0 then HeightFactor := 1.0;
      
      // Смещение пикселя (эффект преломления)
      XMap := X + Round(Dx * HeightFactor * 0.5);
      YMap := Y + Round(Dy * HeightFactor * 0.5);
      
      if XMap < 0 then XMap := 0;
      if XMap >= Width then XMap := Width - 1;
      if YMap < 0 then YMap := 0;
      if YMap >= Height then YMap := Height - 1;
      
      // Вычисляем освещение
      Light := Round((Dx + Dy) * LightIntensity * 0.5);
      Light := Max(-128, Min(127, Light));
      
      // Добавляем блик на высоких волнах
      if Abs(WaveHeight) > 3.0 then
        Light := Light + Round(Abs(WaveHeight)) * 2;
      
      // Получаем пиксель из фона
      if Background <> nil then
      begin
        // Если фон другого размера — масштабируем координаты
        if (BgWidth > 0) and (BgHeight > 0) then
        begin
          BgX := (XMap * BgWidth) div Width;
          BgY := (YMap * BgHeight) div Height;
          if BgX >= BgWidth then BgX := BgWidth - 1;
          if BgY >= BgHeight then BgY := BgHeight - 1;
          BgRowPtr := Background + (BgY * BgWidth + BgX) * 4;
        end
        else
        begin
          BgRowPtr := Background + (YMap * Width + XMap) * 4;
        end;
        
        R := BgRowPtr[1] + Light;
        G := BgRowPtr[2] + Light;
        B := BgRowPtr[3] + Light;
      end
      else
      begin
        // Если фона нет — рисуем волны на чёрном
        R := 100 + Light;
        G := 150 + Light;
        B := 255 + Light;
      end;
      
      R := Max(0, Min(255, R));
      G := Max(0, Min(255, G));
      B := Max(0, Min(255, B));
      
      // Записываем результат
      idx := X * 4;
      if Background <> nil then
        RowPtr[idx] := BgRowPtr[0]  // Alpha из фона
      else
        RowPtr[idx] := 255;          // Alpha = 255
      
      RowPtr[idx + 1] := R;
      RowPtr[idx + 2] := G;
      RowPtr[idx + 3] := B;
    end;
  end;
end;

// ☆☆☆ ГЛАВНАЯ УНИВЕРСАЛЬНАЯ ПРОЦЕДУРА ☆☆☆
procedure WaveProcessorFXBin(
  Pixels: PByte;
  Width, Height: Integer;
  var Waves:TWaveStateArray;
  GridSize: Integer;
  X, Y: Integer;
  Radius: Integer;
  HeightWave: Single;
  Viscosity, WavesSpeed, LightIntensity, Depth: Single;
  Background: PByte = nil;
  BgWidth: Integer = 0; BgHeight: Integer = 0);
var
  NeedFreeBackground: Boolean;
  GradientPixels: array of Byte;
begin
  if Pixels = nil then Exit;
  if GridSize <= 0 then GridSize := 40;
  
  // Проверяем и инициализируем массив волн
  if Length(Waves) <> GridSize * GridSize then
    SetLength(Waves, GridSize * GridSize);
  
  // Инициализация волн при первом вызове
  if Waves[0].Height = 0 then
    InitWaves(Waves, GridSize);
  
  NeedFreeBackground := False;
  
  // Если фона нет — создаём градиент
  if Background = nil then
  begin
    SetLength(GradientPixels, Width * Height * 4);
    GenerateGradient(@GradientPixels[0], Width, Height);
    Background := @GradientPixels[0];
    BgWidth := Width;
    BgHeight := Height;
    NeedFreeBackground := True;
  end;
  
  // Если заданы координаты — создаём волну
  if (X >= 0) and (Y >= 0) and (Radius > 0) and (HeightWave > 0) then
    MakeRipple(Waves, GridSize, X, Y, Radius, HeightWave);
  
  // Симуляция физики волн
  SimulateWaves(Waves, GridSize, Viscosity, WavesSpeed);
  
  // Рендеринг в пиксели
  RenderWavesToPixels(
    Waves, GridSize,
    Pixels, Width, Height,
    Background, BgWidth, BgHeight,
    LightIntensity, Depth
  );
  
  // Освобождаем временный градиент
  if NeedFreeBackground then
    SetLength(GradientPixels, 0);
end;

end.
Ответить