вторник, 4 сентября 2012 г.

GLScene 1.2 Ревизия 6122 Оптимизация достигнутого под Android

Приветствую! С решением проблемы каденсера стало возможным крупномасштабная оптимизация CustomDraw WidgetSet'a. Я позаимствовал класс TJNIEnv из модуля JEDI JNI для того что бы создать надстройку над всеми вызовами и упростить вызовы JNI, а так же убрать большое количества ненужных переменных. Что бы было понятно приведу кусок класса:
  TJNIEnv = class(TObject)
  public
    function JStringToString(JStr: JString): string;   
    function CallObjectMethod(Obj: JObject; MethodID: JMethodID ): JObject; overload;
    function CallObjectMethod(Obj: JObject; AClass: JClass;  const Name: PAnsiChar; const Sig: PAnsiChar ): JObject; overload;
    function CallObjectMethodV(Obj: JObject; MethodID: JMethodID; Args: va_list): JObject;
    function CallObjectMethodA(Obj: JObject; MethodID: JMethodID; Args: PJValue): JObject;   overload;
    function CallObjectMethodA(Obj: JObject; MethodID: JMethodID; const Args: array of const ): JObject;  overload;
    function CallObjectMethodA(Obj: JObject; AClass: JClass; const Name: PAnsiChar; const Sig: PAnsiChar; const Args: array of const ): JObject;  overload;  

Как видно из наличия перезагружаемых директив я немного дополнил методы своими, для упрощения части работы.


Так же я убрал все переменые, содержавшие яваID полей\методов, теперь поиск ID происходит непосредственно перед вызовом метода\поля, например:

var
  javaField_lclmajorversion: JfieldID=nil;
begin
  with javaVMRef.Env do
  begin
  javaField_lclmajorversion:= GetFieldID( javaActivityClass,   'lclmajorversion', 'I');
  self.MajorVersion := Integer(GetIntField( javaActivityObject, javaField_lclmajorversion)); 
 Так же позаимствовал класс ТjavaVM правда как я понял он изначально заточен для работы с самой явой и не годится для андроида, по этому большую часть этого кода я задефайнил позднее можно будет его применить если захотим вторгнутся в webGL или иные Java области.
Ну и переделал этот класс для работы с многопоточностью, что бы он мог выдавать нужный TJNIEnv для своего потока.

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

function TJNIEnv.CallObjectMethod(Obj: JObject; MethodID: JMethodID ): JObject;
begin
  {$IFDEF jnidebug}
  __android_log_write(ANDROID_LOG_INFO, 'TJNIEnv', 'Call: CallObjectMethod');
  {$ENDIF}
  Result := Env^^.CallObjectMethod(Env, Obj, MethodID );
end; 

Это удобно в том плане что некоторые ошибки происходят при вызове JNI и LogCat показывает только ASM код, который вызывает больше вопросов чем ответов. Да, на java уровне, Java выдает достаточно детальную информацию о возникновении ошибки так же последовательность ее возникновения, однако в андроиде нету отладчика нативного кода и нативная библиотека может показать только область памяти что не приемлемо.
В первых пробах править виджетсет у меня такие ошибки вызывали лишь желание откатится назад! Но сейчас становится проще программировать так как знаешь что- где возникло ну или в каком месте следует искать.

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

Ну чтож, это все, спасибо за внимание :)

суббота, 25 августа 2012 г.

GLScene 1.2 Ревизия 6121 Обновления каденсера и механизм многопоточности JNI


Приветсвую! Много воды утекло с момента последнего сообщения, но тем не менее на то было основание. Моя попытка с ходу портировать каденсер провалилась ввиду незнания особенностей работы JNI и Lazarus Widgetseta в целом.
Итак прежде чем рассказать суть решения я ввиду некоторый экскурс по виджетсету jni и связи их с явой.

Первое что мне хочется сказать и поддчеркнуть это то что при програмировании под андроид вы программируете в лазаре «НЕ НАТИВНО». Это значит что результатом компиляции будет не exe исполняймый файл а получится .so библиотека. А так как это будет именно библиотека то для работы с javaой разработчики лазаруса применили механизм экспорта методов

exports
Java_com_pascal_lclproject_LCLActivity_LCLOnTouch name 'Java_com_pascal_lcltest_LCLActivity_LCLOnTouch',

для того что бы из под ява влиять на работу лазаруса и в яве публикуя методы и переменные для того что бы лазарус мог ими управлять посредством JNI.

Причем ключевым моментом здесь является то что при запуске приложения .so библиотека загружается сразу и в ней вызывается секция «initialization» которая по моменту времени является преждевеременной, да, в класическом нативном понимании при старте приложения можно в нее пихать все что хочется но в Андроиде это не допустимо. Недопустимо потому что создание форм и других компонентов происходит по времени значительно позже и привязвается оно к событие Activity OnStart.

Еще одной особенностью является то что все указатели в нативном коде являются локальными и могут жить некоторый пероид времени. Собственно что и произошло при первых попытках принять указатель контекста, и при удалении он оказался не верным, решить проблему удалось закэшировав указатель, тобишь сделав его глобальным при помощи функции NewGlobalRef.

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

При загрузке библиотеки происходит вызов метода JNI_OnLoad в котором этому методу передается виртуальная машина java, указатель которой по большей части статичен в любой момент времени и в любом потоке.
Для работы с ява методоми и полями требуется структура EnvRef в которой будет доступны различные функции для обращения к полям явы, ее можно получить из той же виртуальной машины полученной при загрузке OnLoad.



И для обращения к какой нить функции в яве нужно проделать небольшую работу вот небольшой пример

javaMethod_LCLeglGetError := fjavaEnvRef^^.GetMethodID(fjavaEnvRef, javaActivityClass, 'LCLeglGetError', '()I');
result := EGLint(javaEnvRef^^.CallIntMethod(javaEnvRef, javaActivityObject, javaMethod_LCLeglGetError));

В первой строке мы получаем ID к полю которую хотим вызвать, во второй строке происходит вызов.
В случае если вызов подразумевается производить в различных участках кода и модулей, разумнее всего хранить ID в какой нить переменной.
Однако такой подходе с большим количеством переменных приводит в тупик когда дело доходит до многопоточности.
Вся фишка в том что для своего потока EnvRef уникален, и все вызовы CallIntMethod с ID переменными GetMethodID должны производится своим EnvRef. А так как указатели к ID для другого потока изменились то нужно применить механизм с переменными которые будут уникальны для любого потока.

Для получения структуры EnvRef для конкретного потока необходимо этот поток зарегестрировать в виртуальной машине методом javaVMRef^^.AttachCurrentThread(javaVMRef,@fjavaEnvRef,nil) ;
ну и после всей проделанной работы отцепить.
javaVMRef^^.DetachCurrentThread(javaVMRef) ;

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

Теперь основная проблема, как сделать так что бы в определенном потоке javaEnvRef был свой и так же своими были указатели на ID переменные в яве.

Для решения этой проблемы я пришел к следующему выводу, все переменные получившие ID запихнуть в массив, а для четкого понимания какую ID извлекать из какого элемента массива я все ранее использованные переменные запихнул в типизированный массив.
type
javavariablesid = (
javaField_lcltext,
javaField_lcltitle,

Теперь для того что бы извлеч ID достаточно было указать элемент типизированного массива в числовом виде что и было сделано.
Однако получение ID это пол беды нужно еще получить javaEnvRef для конкретного потока. Что бы особых проблем небыло я создал массив записей в котором было 3 переменных

type
TEnvRefStructureLayerThread = record
EnvID: pointer;
ThreadID: integer;
variablesid: array of pointer;
end;

fArrayEnvRefLayerThread: array of TEnvRefStructureLayerThread;

первое это указатель на структуру EnvRef
второе это Номер потока полученного присвоении EnvRef.
Третье это массив записей с ID ява переменными.

Итак у нас получилась следующая картина 




Позднее после некоторых экспериментов я просто заменил основную переменную javaEnvRef на функцию которая сначала получала GetCurrentThreadId текущего потока потом искала в массиве ThreadID: integer; схожего потока и выводила EnvID: pointer; структуру.

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

Напомню что это было сделано для совместимости внутри лазаруса. Позднее можно будет сделать оптимизацию кода для ускорения работы.

суббота, 30 июня 2012 г.

GLScene 1.2 Ревизия 6106 Откат каденсера и информация по JNI

Приветствую!
В результате изменений каденсер так и не заработал мало того он начал выдавать критические ошибки. Любые попытки привести многопоточность в чувство приводили к ошибками и вот случайно я набрел на просторах инета на эту статью. И еще по цепочке на десяток с других.

Для тех кто не в курсе, сцена на android на данный момент нативно-ненативная. Это значит что инициализация контекста и управление им происходит в JAVA посредством вызовов команд с использованием JNI из lazarusа(Native). Отсюда возможны и баги ибо как оказалось JNI имеет свою специфику но обладает воистину большими возможностями.

 Каждый указатель к JNIEnv  в одном потоке может быт уникален и обращение к объектам java через него в паралельном потоке может вызвать ошибку ибо объект не существует. Мало того нужно привязать привязывать "AttachCurrentThread" паралельный поток к виртуальной java машине что бы можно было производить какие либо операции из паралельного потока.
На практике же вызов команды AttachCurrentThread обернулся критической ошибкой, такое подозрение что указатель на виртуальную Java машину равен = nil или же загвоздка в чем то другом. 
Вобщем все эти ошибки меня изрядно достали и я решил взять небольшой отпуск на недельку-две, попутно собирая теоретическую информацию.

пятница, 22 июня 2012 г.

Парсеры:Опыт массового изменения или преобразование кода.

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


Что бы избежать этого я поделюсь своими опытом в области массового изменения кода в сцене.

Мое самое первое применение массового парсинга и преобразование кода было когда я 3 года назад начал занимаца нашей сценовской вики и встал вопрос как оперативно добавить в вики код сцены с комментариями по шаблону. Для этого я создал проект в котором было 3 TMemo и в 3 прохода программа открывала каждый модуль искала нужные процедуры функции классы и коментарии к ним и делала соответсвующие перестановки. В результате которых получалась готовая вики страница с шаблоном. Программа была не идеальна и я бы сказал быдлокодесркая но она работала, тем не менее проект был закрыт ибо встала другая проблема нужно было такую массу перенести на вики и всю эту кучю страниц мне не хотелось вставлять вручную.




Буквально с годик Ростислав Ярский начал создавать GLScene 1.2 которая могла бы работать на платформе андроид и он сразу же ее имена модулей сделал в стиле C, однако FPC на только рождавшейся платформе андроид не воспринимал данное именование лишь только крайне сырой компилятор без андроида версии 2.7.1 мог это делать. По этому посовещавшись я пришел к выводу что придется переделывать имена модулей с точками на имена с нижним пробелом.
Для применения данной операции я решил сначала создать 2 списка, списка имен модулей с точками и через ТоталКомандер переделать первый список во второй в котором были имена модулей с "_". Затем создав программу парсер я просто напросто заменял во всех модулях встречающейся элементы модулей. Далее что бы это сработало полно я создал скрипт который изменял названия модулей на нужные в SVN репозиторий ибо просто так переименовать модуль нельзя было.
В результате я смог массово переименовать все модули за буквально час+ 2 дня ушло на разработку парсинга, тогда как Ярский потратил на все переделывания вручную целый месяц.

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

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

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

Следует отметить что возможно данная операция не потребовалась бы если бы в лазарусе работали анонимные методы Reference to procedure. А так как их небыло то пришлось добавить 5640 строчек безполезного кода и нужного только для отладки.
Дабы бесполезный код немазолил глаза, я его запихнул в отдельный модуль под названием glcaps.inc и подключал в нужный момент, данный метод работы с массивным кодом я позаимствовал у разрабочиков лазаруса.

Собственно вот так я делал массовые изменения кода если кому то потребуются исходник могу скинуть. Пример перехода от "." к "_" именованиям можно применить и для перехода от GLScene 1.1 к новой и обратно.

Спасибо за внимание! С уважением Рустам Асмандияров!

четверг, 21 июня 2012 г.

GLScene 1.2 Ревизия 6103 Каденсер и потоки

 Приветствую! Почитав вот эту статейку http://mbo88.narod.ru/Ch6.html я решил попробовать отказаться от Synchronized в пользу критических секций. Насколько правильно это решение покажет время и оно будет действовать только для компилятора FPC. У делфи же свой таймер который действует только в пределах винды.

  Так же я добавил в проект и в пакет сцены специальный дефайн -dUseCThreads он позволяет подключить многопоточность в UNIX приложениях, без него каденсер и асинк таймер работать не будут. В виндовсе данный дефайн не будет применен ибо ограничен дефайном Unix.

Думаю на этом все, пора собрать демку и выложить ее в ютюб как доказательство работы сцены в платформе Android, но это не означает что работа прекращена, нужно починить рендер для EGL 1.1 и улучшить логинг ну а так же начать изменения в ядре!

GLScene 1.2 Проблемные Потоки!

Компоненты ТCadencer И TAsyncTimer основаны на использовании смеси потоков и fpgettimeofsec. Это позволило в сцене под FreePascal увеличить скорость работы каденсера до уровня делфи.

Однако потоки в Линуксе это совершенно другая стезя они вроде как работают но можно ожидать нежданчика в виде  'Suspending one thread from inside another one is unsupported (because it is unsafe and deadlock prone) by *nix and posix operating systems' это как я понял означает что нельзя останавливать поток что бы выполнить какое то действие в основном потоке.
Это же самое происходит и с каденсером под андроид. Как выход использовать функции близкие к библиотекам без стороннего кода и разбиратся в понимаии потоков иначе не видать нам каденсера и сцены!

среда, 20 июня 2012 г.

GLScene 1.2 Ревизия 6101 ViewPort

В Андроид яве есть своя иерархия и свои особенности.

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

В результате я отказался от получения размеров экрана с поверхности SurfaсeView и стал брать их напрямую через
   


       DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
             lWidth = metrics.widthPixels;
            lHeight = metrics.heightPixels; 

Так же пришлось убрать из кода лазруса непонятное задание размеров формы, ибо туда передавался веголишь xdpi и xwidth каким макаром вычесляись размеры высоты не понятно но они не осответсвовали реальным размерам.

Таким образом осталось исправить 2 проблемы, это
1) не работающий Synchronyze в потоковом менеджере каденсера.
2)Переделать поддержку OGL 1.1 так как текущий рендер не держит дисплейные списки.
3)Найти мелкие баги которые приводят к тому что текстура лампочки не корректно отображается.

суббота, 16 июня 2012 г.

Первое сообщение

Приветствую! Это мой первый блог и вообще опыт работы с блогами, до этого я нигде не бложил, и по этому все это для меня в новинку. Так что если где увидите косячек, особо не ругайте!
С Уважением Рустам Асмандияров.