Target Windows dla tangram-es

Tangram to jedna z lepszych alternatyw google/bing maps, do tego otwarta i działająca nie tylko w przeglądarce (JS + WebGL), ale tekże w wersji pod OpenGL-ES dla Linuksa, OSX, Androida i iOS. Dodatkowo projekt jest otwarty, więc można go rozszerzać pod własne potrzeby. Niestety, wersji na Windows brak. Jako, że używałem tych map w aplikacji androidowej i sprawdzała się tam nieźle, stwierdziłem, że w imię jednolitości opracuję target dla Windows. Żeby cały wysiłek miał jakiś walor dydaktyczny, pisałem ten artykuł równolegle z kolejno wykonywanymi rzeczami.

Rozeznanie

Pierwsza rzecz, to pobieżne przepatrzenie kodu i opracowanie strategii dalszego działania. Na daną chwilę repozytorium posiada katalog platforms, w którym to są przykładowe aplikacje dla danych systemów. Widać też, że aplikacja linuksowa korzysta z GLFW, który również dostępny jest dla Windows. No, może nie będzie źle.

Druga rzecz rzucająca się w oczy to sposób budowania. W instrukcjach dla platformy linuksowej używa się instrukcji make linux . Drążąc temat dalej widać, że makefile generowany jest przy użyciu CMake. Trzeba będzie go trochę zmodyfikować, by przyjmował target windowsowy.

Przygotowanie platformy windows

Skopiowałem sobie platformę linux i nazwałem windows, zmieniłem nazwy linuxPlatform na windowsPlatform. I tu pojawił się pierwszy commit, by móc śledzić diffa.

Wywaliłem odniesienia do sys/resource.h i sys/syscall.h. Ifdefy linux/raspi też nie są potrzebne, z tym, że zostawiłem kod zawarty w ifdefie linuksowym w initGLExtensions – Windows raczej obsłuży to samo. Dodatkowo pozbyłem się funkcji setCurrentThreadPriority(). Raz, że nie była używana, dwa że jest mocno związana z linuksem, trzy że raczej na początek nie będzie potrzebna.

Pozbyłem się też obsługi sygnału CTRL+C z pliku głównego. Być może później będę musiał to dodać ponownie, zatem tę pojedynczą zmianę zrobiłem jako oddzielny commit. Będzie to wyraźniej widać w historii.

Poprawki CMake

Jeśli chodzi o platformę, główny plik CMake ogranicza się do sprawdzenia aktualnej platformy i upewnienia się, że jest ona na liście obsługiwanych platform. Dodając windows  do listy platform spowodowałem próbę dołączenia pliku toolchains/windows.cmake , który oczywiście nie istnieje. Stworzyłem go kopiując linux.cmake i robiąc znajdź-zamień na wyrazie “linux”. Moją uwagę jednak skupiła linia add_definitions(-DTANGRAM_LINUX) . Szukając TANGRAM_LINUX  natknąłem się na wystąpienia tylko w platforms/linux (więc niegroźnie) i platform_gl.h w platforms/common. Brzmi jak obietnica zabawy. Tak czy inaczej, zmieniłem TANGRAM_LINUX  na TANGRAM_WINDOWS . Zabawa z CMake prawodpodobnie tu się kończy.

Poprawki platform_gl

definicja TANGRAM_LINUX  używana jest tylko w jednym miejscu:

#ifdef TANGRAM_LINUX 
#define GL_GLEXT_PROTOTYPES 
#include <GLFW/glfw3.h> 
#endif // TANGRAM_LINUX 

Jako, że target windowsowy również ma korzystać z GLFW, zamieniłem to na coś takiego:

#if defined(TANGRAM_LINUX) || defined(TANGRAM_WINDOWS)
#define GL_GLEXT_PROTOTYPES
#include <GLFW/glfw3.h>
#endif // defined(TANGRAM_LINUX) || defined(TANGRAM_WINDOWS)

Odpalając CMake . -G “MinGW Makefiles”  któryś z kolejnych cmake’ów podprojektów nakrzyczał na mnie, że nie wolno budować w katalogu głównym. Założyłem zatem katalog build i z jego poziomu zrobiłem CMake .. -G “MinGW Makefiles” . Ruszyło, zaczęło budować…

Błędy przy kompilacji

… I wysypało się w połowie drogi.

D:\prg\_git\tangram-es\core\deps\harfbuzz-icu-freetype\harfbuzz\src\hb-common.cc:224:37: error: 'strdup' was not declared in this scope
     lang = (hb_language_t) strdup (s);
                                     ^

Chwila guglania i rzeczywiście – dany plik nie inkluduje string.h ani cstring, w którym leży funkcja strdup. To jednak nie rozwiązało problemu, zatem zacząłem szukać głębiej i to nie inklud był przyczyną – kompilator uruchomiony był z flagą -std=c++1y  i o dziwo w takiej konfiguracji nie ma funkcji strdup – zarówno w formie zwykłej jak i z prefiksem std:: . Zmiana flagi kompilacji na -std=gnu++1y  w windows.cmake zrobiła swoje. Kolejny przystanek w 83%:

D:\prg\_git\tangram-es\core\src\gl\primitives.cpp:12:31: fatal error: debugPrimitive_vs.h: No such file or directory

No i faktycznie, pliku nie ma. Patrzę w log kompilacji a tam coś takiego:

Scanning dependencies of target debugPrimitive_vs
[  8%] Generating ../../core/generated/debugPrimitive_vs.h
[  8%] Built target debugPrimitive_vs

No i widać, że ścieżka się nie zgadza. Zapuściłem make’a tym razem z katalogu build/windows, tak by dwa piętra wyżej leżał katalog core. Druga sprawa – podczas kompilacji uruchamiany był program incbin.sh, który nie działał, gdyż nie mam interpretera .sh. Okazuje się, że incbin to wspaniały kawałek kodu, który jest jednocześnie poprawnym skryptem bat i sh domyślnie występujący z rozszerzeniem .bat i nie wiedzieć czemu ekipa od tangram-es zmieniła mu rozszerzenie na .sh łamiąc kompatybilność z Windowsem. Pierwsze co, to przywróciłem rozszerzenie .bat i oryginalną treść. Druga rzecz, to drobna modyfikacja CMakeLists.txt tangram-es/core by zamiast polecenia incbin  używał ${INCBIN}  zdefiniowanego jako:

if(WIN32)
	set(INCBIN "${PROJECT_SOURCE_DIR}/../scripts/incbin.bat")
else()
	set(INCBIN "sh ${PROJECT_SOURCE_DIR}/../scripts/incbin.bat")
endif()

No i ruszyło i zatrzymało się na 98%:

D:\prg\_git\tangram-es\core\src\util\zlibHelper.cpp:3:18: fatal error: zlib.h: No such file or directory

Naprawiłem to doinstalowując port windowsowy zlib i dodając do CMake’a szukanie i dołączenie jego plików. Poszło dalej, aż nie zaczął się wywalać platforms/common/platform_gl.cpp krzycząc różne dziwne rzeczy o redefinicjach symboli OpenGLa i braku niektórych funkcji. Zwłaszcza ten drugi problem to gigantyczna słabość Windowsa – w sposób podstawowy nagłówki OpenGLa zapewniają wyłącznie stare API do wersji 1.4, jeszcze sprzed rewolucji shaderowej, gdzie dzisiaj już jest wersja 4.5. Pozostałe funkcje trzeba sobie załadować we własnym zakresie.

Przypomniało mi się, że GLFW na swojej stronie zalecał jakiegoś loadera dodatkowych funkcji OGL. Chwila sprawdzania i wyszło na to, że był to GLAD – wraz z praktycznym generatorem plików do dołączenia do projektu. Wygenerowałem sobie goły GLES2.0 z loaderem. Przy kompilacji zaczął krzyczeć z powodu redeklaracji, bo GLFW jakimś cudem dalej ładował nagłówki systemowe. Zgodnie z informacjami w FAQ projektu, GLFW powinien sam wykryć loadera, lecz jeśli tego nie zrobi, należy zdefiniować GLFW_INCLUDE_NONE . Tak też zrobiłem i błędów ubyło, lecz jeszcze nie wszystkie – brakuje m.in. funkcji glMapBuffer, która nie jest częścią standardu. Zaznaczam wszystkie możliwe rozszerzenia i dalej nic – coś jest nie tak.

Okazuje się, że dla innych targetów niektóre niestandardowe rozszerzenia miały zdefiniowane aliasy. Przykładowo dla OSX:

#define glDeleteVertexArrays glDeleteVertexArraysAPPLE
#define glGenVertexArrays glGenVertexArraysAPPLE
#define glBindVertexArray glBindVertexArrayAPPLE

Wykonałem podobne definicje dla brakujących funkcji, gdyż GLAD deklarował je z końcówką OES. Pomogło. Następny przystanek – cURL. Doinstalowanie pakietu poszło łatwo, ciężej żeby zmusić CMake’a do jego znalezienia, gdyż skrypt szukania cURLa nie wykorzystuje ścieżki instalacji. Z pomocą przychodzi zmienna środowiskowa CMAKE_PREFIX_PATH, która mówi CMake’owi gdzie ma szukać inkludów i libek. I to w sumie na tyle błędów kompilacji. Pozostało coś gorszego…

Błędy linkowania

CMakeFiles\tangram.dir/objects.a(urlClient.cpp.obj):urlClient.cpp:(.text+0x69): undefined reference to `_imp__curl_global_cleanup'
CMakeFiles\tangram.dir/objects.a(urlClient.cpp.obj):urlClient.cpp:(.text+0xb72): undefined reference to `_imp__curl_easy_init'
CMakeFiles\tangram.dir/objects.a(urlClient.cpp.obj):urlClient.cpp:(.text+0xb78): undefined reference to `_imp__curl_easy_setopt'
CMakeFiles\tangram.dir/objects.a(urlClient.cpp.obj):urlClient.cpp:(.text+0xf05): undefined reference to `_imp__curl_easy_perform'
CMakeFiles\tangram.dir/objects.a(urlClient.cpp.obj):urlClient.cpp:(.text+0xf36): undefined reference to `_imp__curl_easy_getinfo'
CMakeFiles\tangram.dir/objects.a(urlClient.cpp.obj):urlClient.cpp:(.text+0x1095): undefined reference to `_imp__curl_easy_cleanup'

Chwila guglania i okazuje się, że funkcje poprzedzone _imp_ są odwołaniami do funkcji w DLL. Poszukałem jeszcze trochę i okazuje się, że może być to spowodowane korzystaniem przez MinGW z bibliotek budowanych w Visual C++ . Ściągnąłem źródła cURLa, skompilowałem komendą make mingw32 i ruszyło.

lib/libtangramcore.a(tileWorker.cpp.obj):tileWorker.cpp:(.text+0x487): undefined reference to `Tangram::setCurrentThreadPriority(int)'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0xcbd): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0xcdd): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0xd93): undefined reference to `__stack_chk_fail'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0xe03): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0xf47): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0x11fd): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0x1384): undefined reference to `__stack_chk_fail'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0x15f5): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0x166d): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0x167e): undefined reference to `__stack_chk_fail'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0x1709): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0x17be): undefined reference to `__stack_chk_guard'
lib/libSQLiteCpp.a(Database.cpp.obj):Database.cpp:(.text+0x17d0): undefined reference to `__stack_chk_fail'

Odezwała się funkcja setCurrentThreadPriority, którą wyrzuciłem, więc ponownie ją dodałem z pustym ciałem funkcji. Co do reszty: sqlite było kompilowane z flaga -fstack-protector zaś reszta nie. Dodanie tej flagi do windowsowego cmake’a załatwiło sprawę. Exe gotowe, ale to nie koniec.

Problemy po uruchomieniu

Aplikacja się nie chciała uruchomić z braku paru .dll od zliba i cURLa. Po zaciągnięciu ich aplikacja ruszyła, jednak w oknie konsoli przywitała mnie salwa errorów typu:

ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 2 failed: 'SSL certificate problem: unable to get local issuer certificate' with http status: 0 for url: https://fonts.gstatic.com/s/opensans/v13/wMws1cEtxWZc6AZZIpiqWALUuEpTyoUstqEm5AMlJo4.woff
ERROR D:\prg\_git\tangram-es\core\src\scene\sceneLoader.cpp:774: Bad URL request for font open sans_400_normal at URL https://fonts.gstatic.com/s/opensans/v13/wMws1cEtxWZc6AZZIpiqWALUuEpTyoUstqEm5AMlJo4.woff
ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 3 failed: 'SSL certificate problem: unable to get local issuer certificate' with http status: 0 for url: https://tile.mapzen.com/mapzen/vector/v1/all/16/19294/24642.mvt?

O ile te drugie związane mogą być z tym, że same w sobie linki nie działają, gdyż nie kompilowałem z podaniem klucza API, o tyle te od gstatic pokazują, że cURL ma sam w sobie problemy z HTTPSem. Stwierdziłem, że to zapewne dlatego, że korzystam z gotowca curla. Skompilowałem go sobie ręcznie i otrzymałem tego typu errory:

ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 1 failed: 'Protocol "https" not supported or disabled in libcurl' with http status: 0 for url: https://fonts.gstatic.com/s/montserrat/v7/zhcz-_WihjSQC0oHJ9TCYL3hpw3pgy2gAi-Ip7WPMi0.woff
ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 2 failed: 'Protocol "https" not supported or disabled in libcurl' with http status: 0 for url: https://fonts.gstatic.com/s/opensans/v13/wMws1cEtxWZc6AZZIpiqWALUuEpTyoUstqEm5AMlJo4.woff

ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 2 failed: 'Protocol "https" not supported or disabled in libcurl' with http status: 0 for url: https://tile.mapzen.com/mapzen/vector/v1/all/16/19295/24642.mvt?
ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 0 failed: 'Protocol "https" not supported or disabled in libcurl' with http status: 0 for url: https://tile.mapzen.com/mapzen/vector/v1/all/16/19294/24642.mvt?
ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 3 failed: 'Protocol "https" not supported or disabled in libcurl' with http status: 0 for url: https://tile.mapzen.com/mapzen/vector/v1/all/16/19294/24641.mvt?
ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 1 failed: 'Protocol "https" not supported or disabled in libcurl' with http status: 0 for url: https://tile.mapzen.com/mapzen/vector/v1/all/16/19295/24641.mvt?
ERROR D:\prg\_git\tangram-es\platforms\common\urlClient.cpp:172: curlLoop 2 failed: 'Protocol "https" not supported or disabled in libcurl' with http status: 0 for url: https://tile.mapzen.com/mapzen/vector/v1/all/16/19294/24643.mvt?

Okazało się, że makefile pod mingw w ogóle nie ma opcji ustawienia ssl. Po dohackowaniu sobie obsługi ssl i zlib wróciłem do tych samych problemów z certyfikatami SSL, zatem to nie było to. Wygląda na to, że nie ma wkompilowanego certyfikatu, więc żeby się z tym w tej chwili dłużej nie bawić dodałem omijanie sprawdzania certyfikatu po stronie tangrama:

curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, FALSE);

No i ruszyło, ale to nie koniec. Pojawiły się crashe. Po skompilowaniu tangram-es w trybie debug, GDB mi podpowiedział co nieco:

D:\prg\_git\tangram-es\build\windows\bin>gdb tangram.exe
GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "mingw32".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from D:\prg\_git\tangram-es\build\windows\bin/tangram.exe...done.
(gdb) run
Starting program: D:\prg\_git\tangram-es\build\windows\bin/tangram.exe
[New Thread 18296.0x4940]
[New Thread 18296.0x3fb8]
[New Thread 18296.0x3388]
[New Thread 18296.0xe5c]
...
Program received signal SIGSEGV, Segmentation fault.
warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

?? (warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

) at D:/prg/kompilatory/TDM-GCC/lib/gcc/mingw32/5.1.0/include/c++/bits/move.h:76
76          forward(typename std::remove_reference<_Tp>::type& __t) noexcept
warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

(gdb) bt
warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

#0  ?? (warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

) at D:/prg/kompilatory/TDM-GCC/lib/gcc/mingw32/5.1.0/include/c++/bits/move.h:76
warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

#1  0x00403033 in Tangram::GL::mapBuffer(unsigned int, unsigned int) ()
#2  0x004a8ad5 in Tangram::MeshBase::subDataUpload(Tangram::RenderState&, signed char*) ()
    at D:\prg\_git\tangram-es\core\src\gl\mesh.cpp:107
#3  0x007d984c in Tangram::DynamicQuadMesh<Tangram::SpriteVertex>::upload(Tangram::RenderState&) ()
    at D:/prg/_git/tangram-es/core/src/gl/dynamicQuadMesh.h:92
#4  0x004817a0 in Tangram::PointStyle::onBeginFrame(Tangram::RenderState&) ()
    at D:\prg\_git\tangram-es\core\src\style\pointStyle.cpp:58
#5  0x00411aab in Tangram::Map::render() () at D:\prg\_git\tangram-es\core\src\map.cpp:498
#6  0x00404d70 in Tangram::GlfwApp::run() () at D:\prg\_git\tangram-es\platforms\common\glfwApp.cpp:129
#7  0x00401b0a in main () at D:\prg\_git\tangram-es\platforms\windows\src\main.cpp:30
warning: (Internal error: pc 0x0 in read in psymtab, but not in symtab.)

Jak widać kłopoty sprawia funkcja glMapBuffer – jedna z tych, które musiałem ręcznie dodefiniować. Chwila guglania i ktoś na stacku wypowiada się, że GLES2.0 jest w sumie kompatybilny z GL3.4. Wygenerowałem GLADa dla tej wersji i zrekompilowałem. Teraz tangram tylko krzyczy, że nie zna dwóch funkcji będących w GLES2 a dodanych dopiero w GL4.0. Jako, że funkcje te po prostu są kopiami innych funkcji przyjmującymi argumenty float zamiast double, zdefiniowałem je makrami aby nie zwiększać zbytnio wymagań aplikacji:

#define glDepthRangef(a, b) glDepthRange((double)(a), (double)(b))
#define glClearDepthf(a) glClearDepth((double)(a))

I na tym historia się kończy.

Epilog

Z tego, co naczytałem się issues projektu, target windowsowy był dość długo oczekiwaną funkcjonalnością. Dziwi mnie, że nikt tego wcześniej nie zrobił. Ba, dziwi mnie, że projekt od samego początku nie supportuje windowsa, zwłaszcza że korzysta z GLFW, które z automatu załatwia wszelką kompatybilność. W międzyczasie wyszła kolejna wersja tangrama, więc będę musiał dosynchronizować się przed pull requestem. No i skompilować OpenSSL z certyfikatem, by móc sprawdzać wiarygodność źródeł.

Następny przystanek: kontrolka tangram-es dla wxWidgets.

This entry was posted in tangram-es and tagged , , , , , , , , , . Bookmark the permalink.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.