3DO і Android NDK і як би не вляпатися...

image

Напевно знайдеться не мало додатків, які майже неможливо зробити на Java, в силу великої вихідної кодової бази C++ або вимог до продуктивності. І так вийшло, що я розробляв одне з таких додатків, а саме емулятор ігрової консолі 3DO – Real3DOPlayer. В моєму випадку роль грала як кодова база, так і вимоги до продуктивності. Код базувався на моєму десктопному проекті «Фенікс», і він гальмував навіть на середніх десктопах, не те що на процесорах вбудовуються. Скільки проклять виривалося на адресу корпорації Gooogle я вже і не пам'ятаю, але досвід я отримав безцінний, яким і хочу поділитися.


Новачок, б'ється лобом об стіну...
Gex

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

Шишка перша. Встановивши Android Studio під Windows, я вирішив накидати найпростіший «Hello, world!» з викликом JNI. Все дуже просто:

JNIEXPORT jstring JNICALL Java_ru_arts_union_real3doplayer_nativecore_stringfromjni(JNIEnv* env, jclass clazz)
{
return env->NewStringUTF("Hello, World!");
}


Помилка компіляції. Просто помилка компіляції. Розумієте, код компілюється і крапка. День я намагався скомпілювати код, активно гугля проблему, але по закінченню дня код так і не вдалося зібрати. На наступний день взяв ноутбук з Linux і вирішив продовжити мучити ці два рядки, на мій подив, всі одразу ж зібралося. Почав читати форуми, чому під Windows не можна зібрати код? Виявилося — одного файлу C/CPP мало для складання проекту, потрібно як мінімум 2!

Шишка друга. Запускаю програму, а воно падає, повідомляючи, що метод stringFromJNI не знайдено. Як так не знайдено?! Ось же він, прямо тут і дізассемблер видно! Не панікувати, дивлюся весь код від і до, слава богу, його не так вже багато. Дивлюся на префікс в назві функції ru_arts_union_real3doplayer, і щось мені підказує, що щось тут не так… Адже ім'я мого пакету виглядало так: ru.arts-union.real3doplayer, а ім'я методу кодує крапку і тире однаково, це не добре, і дивно, що саме середовище допустила таке ім'я пакета. Міняємо ім'я пакета і методу, прибравши тире. Ура! Метод бачиться, і через два дні працює «Hello, world!».

Шишка третя. Не біда, файлів буде багато, назва поправили, їдемо далі, думаючи, що найстрашніше позаду. Додаємо свої вихідні коди, які перевірялися під Linux/Windows для різних процесорів (в тому числі ARM). Все відмінно скомпилировалось, без єдиної помилки. Додаток падає при старті без будь-яких попереджень. Продовжуючи мидитировать над кодом, спостерігаю проплывающую рядок з long double, згадую, що NEON максимум тримає double, але цей код навіть не викликається, він просто є, а в заголовку типів я видалив real80. І потім, код адже скомпилировался без помилок! Хм.., ніж стукати в бубон, краще выпилю цей код… Запуск, і бінго! Код працює, ігри заускаются, хоч і з дикими гальмами!

Сюди ж варто згадати роботу з здавалося б банальними речами — стандартними бібліотеками мови, будьте обережні, досить часто трапляється так, що деякі методи, які присутні в одній версії — відсутні в іншій, і ваш додаток падає з-за неможливості завантажити потрібний метод. Тут допоможе тільки велике тестування або відмову у користь STL зі статичною линковкой. Так, наприклад, TinyXML падає на досить великій кількості пристроїв з-за відсутності функцій перетворення чисел в рядок і назад.

Чим менше у вас досвіду, тим більше у вас буде шишок — будьте морально готові, напевно я не все пригадав…

У гонитві за продуктивністю або вляпався раз...
BC Racers

Як збільшити продуктивність емулятора, який місцями напружував навіть i7? Дуже просто, потрібно обмежити себе обчислювальним ресурсом, що і сталося з спробою перенести її на Андроїд.

У такій непростій ситуації довелося реалізувати кешування текстур з попередньо частково або повністю обробленими пікселями графічним процесором приставки; тріангуляцію квадратних полігонів (у 3DO нелінійне накладення текстур); статичну рекомпиляцию коду, часто використовуваних бібліотек ОС консолі; розпаралелювання емуляції підсистем консолі. Все це дозволило знизити апаратні вимоги більш ніж в три рази. Де ще можна заощадити такти, не рахуючи класичних технік, оптимізації на асемблері і тривалого просиджування в профайлер? Очевидно, на те, що більшість користувачів не помітить різниці. Так, наприклад, я добре заощадив на 16-бітному растрі, замість 24-бітного, при заповненні кадру.

Сам процес оптимізації дуже цікавий і тягне на окрему статтю, але все вищесказане не відноситься до Андроїду як до такого. А що ж може дати нам сама платформа та її особливості? Очевидно пряму запис в текстурну пам'ять, оскільки CPU і GPU на мобільних платформах в переважній більшості пристроїв її поділяють. Але на жаль, даний механізм, відомий як GraphicBuffer, не призначений для загального користування! Нічого, його можна дістати через dlopen. Але робити це треба опціонально, оскільки цей хак може працювати не скрізь. Реалізацію можна глянути тут (досить непросто знайти через пошук): android.googlesource.com/platform/external/deqp/+/deqp-dev/framework/platform/android/tcuAndroidInternals.cpp.

А що нам дуже не хоче давати Андроїд, але дуже треба? Звичайно контроль над циклом програми! Але, якщо дуууже треба, то допоможе NativeActivity або ось цей ось приклад, який підкуповує своєю простотою і зручністю: github.com/tsaarni/android-native-egl-example. На відміну від NativeActivity, з яким незрозуміло як працювати з звичайного Activity, даний підхід дозволяє захопити контекст вікна і малювати в окремому потоці, тоді, коли нам треба, а не коли вирішить Java. І ось тут-то я вляпався, сам того не знаючи.

Справа в тому, що цей механізм працює не на всіх версіях Андроїда (принаймні з моїх тестів, версії 4.1-4.3 його не підтримують), а це близько 40% цільових пристроїв для мого додатка, судячи за статистикою Гугла, тобто 40% прибутку геть. І дізнатися ви про це можете дуже пізно, адже зазвичай користувач поставивши додаток і побачивши, що воно не працює, відразу зносить його і не пише відкликання, воно і зрозуміло, адже треба швидше повернути гроші. Виправляти ж неправильний цикл програми — справа не з приємних, адже гарантовано визначити на якому пристрої він працює, а на якому немає — не можна ставити за замовчуванням більш повільне рішення, але більш сумісний — можна, але карму ви собі попортите, адже не кожен полізе в налаштування перевіряти: «А чи не можна щось підкрутити, щоб стало як раніше і не гальмувало?» Краще відразу зробити більш дубовий варіант, а потім вже додати опцію, але в моєму випадку вже пізно.

Скорочуючи сутності вляпався два...
Bust-a-Move

Досить часто в SDK вже є функціонал, який не хочеться дублювати в нативному коді, наприклад, для роботи зі шрифтами або зображеннями, та багато чого, адже у Java величезна бібліотека. І виникає закономірне бажання зробити виклик Java-методу з C++. У мене як раз таке бажання виникло, і я зробив класну річ — рендер шрифтів текстуру за запитом з нативного коду, все протестував на своїх пристроях і купі віртуальних пристроїв. Все відмінно працювало, і вкотре вляпався — зробив реліз… У приблизно 1% користувачів вже купили додаток) програма почала крашиться. Це було, м'яко кажучи — дуже погано, негативні відгуки, та й сама ситуація — чоловік заплатив і тут бац — у нього не працює. В чому причина, сказати важко, підозрюю деякі виробники використовують модифіковану вісь або щось ще. Помилку у себе я звичайно не виключаю, але дивлячись на такий ось рапорт, я сильно в цьому сумніваюся (зрозуміло метод такий був, чорним по білому):

java.lang.NoSuchMethodError: no method with name='loadConfig' signature='(Ljava/lang/String;)Ljava/lang/String;' in class Lru/vastness/altmer/real3doplayer/MainActivity;
at dalvik.system.NativeStart.run(Native Method)


Довелося дуже швидко випилювати це рішення та замінювати іншим. Обійшлося малою кров'ю. Саме по собі рішення дуже хороше (1% несумісності можна стерпіти, просто пройдуть повз і не куплять), але тільки до запуску програми, потім, коли продано вже багато копій — я б настійно радив.

Оновлюючи арсенал або вляпався три...
Poed

Переставивши систему, я поставив SDK поневее і, відповідно, targetSdkVersion теж поміняв. Тут же трапилася неприємність. Перестали бачитись SD-накопичувачі, що викликано дивними махінаціями Гугла з правами доступу. При цьому завантаження АПК з більш старою версією SDK виявилася можливою! Не можна! Але я відбувся легким переляком, проблема вирішилася запитом дозволів. А якщо проблема не вирішилася б так легко? Що робити тоді? Питання дуже цікаве…

Сама ж неприємна річ, яка прийшла з оновленнями, від якої не можу позбутися донині — це небажання середовища оновлювати APK при змінах в нативної бібліотеці, проблему спостерігаю і під Linux і Windows. Це викликає роздратування, тепер щоб на пристрої в процесі налагодження з'явився актуальний свіжозібраний АПК, треба виконати наступне (Android Studio 2.1.1):

1. Перезібрати проект.
2. Запустити проект (при цьому він пересоберется ще раз, якщо перший крок пропустити — не пересоберется).
3. Зупинити програму.
4. Зібрати АПК.
5. Запустити додаток.

А ви думали, якщо зробили ребилд, то запустивши додаток ви отримаєте актуальний свіжозібраний APK на пристрої? Не тут-то було, перевіряйте, а краще зробіть собі спливаюче віконце або повідомлення в лог з версією змін. Інакше ризикуєте втратити купу часу на перевірки свого справного коду. І не сподівайтеся, що з другого або третього запуску вийде.

Тим у кого подібні проблеми, в якості компенсації часу, якщо цього ще не зробили, раджу вказати jobs N для паралельного складання нативного коду.

У сухому залишку...
Robinsons Requiem

Я взагалі в осаді від засобів розробки на цю платформу, такий кривизни вже років десять не бачив, а мені є з чим порівнювати. Я звичайно розумію — Java в пріоритеті, але все ж… З цим всім може потягатися хіба що знайдений мною багато років тому баг в интеловском компіляторі при зсуві на нуль змінної, з тих пір ні, ні, та напишу:

if(shift)return x>>shift;
return x;


Тим не менш, варто віддати належне Гуглу, вони зробили все, щоб зарплати у нативних розробників під Андроїд були високими!

Для мене, як С++ розробника, ситуація з додатками під Андроїд виглядає парадоксальним чином, з одного боку, потрібні механізми є, але сукупний обсяг проблем у платформі такий, що при спробі зробити які-небудь поліпшення, я повинен вибирати між втратою репутації і розвитком програми. Ні одна платформа не ставила мене перед таким вибором.

Мимоволі ловиш себе на думці, що може краще нічого не покращувати під цю платформу, а то як би чого не сталося…

ПС. Не подумайте — емулятор буде розвиватися і далі, просто накипіло.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.