flascc移植問題流水賬


 把demo順利移植到幾個pc和移動平台后,便不覺雄心博博,想要一股作氣,把flash“平台”也拿下。但flash終規不是一個“正常的”posix類平台,adobe相對其它幾家操作系統級廠商來說,在打造平台環境及工具鏈方面的功力確實還是差了點,導致困難重重,其編譯調試過程更是讓人叫苦連天,至今仍在苦苦掙扎中。現打算每天一記,把所有的陷井和進步都總結在案,看看需要多少天才能克盡全功!

 12.6 今天算是有了較大進展,可以加載一個外部圖片,並畫出來了!

  由於是第一次寫,先補記一下這之前的所有經歷。大概在三周前,由同事Z開始着手第三方庫的移植,如iconv/freeimage/freetype/lua等等。由於之前在做其它平台時已經折騰過好幾遍,知道這類開源庫的交叉編譯都有大同小異的流程,因此雖然繁瑣,但也還正常,沒有碰到太多詭異問題。令我留下印象的有兩件事,一是有些庫在生成.a文件時用的是AR,有的是用RANLIB,在配置交叉編譯環境時應該兩個都寫上,否則剛好漏掉它需要的那個時,生成的.a文件其實是用本機(native)格式的,最終鏈接時肯定不過,會提示can't find symbol table之類的信息。二是在最后生成可執行文件時,出了一個unresolved extern:avm2_tramp_alloc。當時找遍所有標准庫,都沒發現哪個里面有這符號,在google上也搜不到任何信息,正當焦頭爛額時,偶然發現sdk/share下面有個avm2_tramp.cpp文件,原來需要的函數定義就在這里面了,把它加到編譯列表里,鏈接就成功了。但是也沒有深究這個文件到底是做什么用,以及為什么會采用這種提供方式。(要做的事太多了,還來不及打這種支線劇情)。

  其實鏈接成功的只是一個簡單的空殼程序,還完全不包含demo的任何內容,但是這里面對主循環和消息分發以及系統窗口等概念做了提煉,並統一到引擎的系統接口層中。這個接口層是一個用來屏蔽不同系統對應用程序生命周期管理的中間模塊,旨在為其上的邏輯層提供統一的入口點、事件機制。一般的系統都是由main函數開始,需要自己建立事件循環並輪詢之,但是有的系統自建了事件循環和分發機制,如果不屏蔽這個差別,那么在寫上層應用時,就不可避免的還要涉及平台條件編譯問題,很不美觀。而屏蔽的結果就是,寫應用時不再從main/winmain開始了,變成了像MFC一樣繼承一個app類並定義一個該類的全局對象來啟動。多年前也不明白MFC種種被人詬病的手法是出於何故,只有當自己真正做到時,才理解前人的一片苦心。

  在這個空殼能在flashplayer里運行起來后,就開始想辦法往里面一點點加東西,當然最重要的是都顯示出來,讓每次加的東西都能看到有什么不同。因為其它平台的基礎功能都早已經做好,demo已經做到了稍顯復雜的程度,想直接拿來測試肯定是不行的。於是為它專門寫了另一個小程序,先不跑腳本和對象系統,而是直接在初始化時創建一次資源,在幀函數里繪制它們。

  首先當然是用stage3d的api來做,先確認功能本身是正常的。參照sdk中的例子,刷出顏色,畫了三角形后,大家都舒了一口氣,因為在把自己的程序編成flash后能看到東西(其實還沒涉及到半點自己的邏輯),剩下的就是慢慢調了,於是覺得用C寫flash這回事也該算是靠譜了。可后面的事情證明這口氣還真是舒得早了點……

  確認stage3d功能正常后,就開始想法將其封裝到統一的抽象接口之后。因為需要跨渲染api,所以對邏輯層定了一套渲染抽象接口,並已經有了dx9/opengl/opengles三套實現,現在需要新增一套用於flash的實現。封裝的過程中碰到幾個問題:

  1)一開始沒搞懂VertexBuffer相關的幾個接口里參數的含義,誤以為flash只支持多流頂點,然而我在邏輯層都是以單流方式使用的,於是只好在實現as3mesh.unlock_vertex時,再手動拆分一次頂點到多個數組。雖然已經做好了但后來才發現setVertexBufferAt這個api的第3參數offset,其實是指不同屬性在同一數組中的偏移(這正是單流的關鍵),而之前一直誤以為是起始頂點偏移,於是很快把它改回來了。

  2)IndexBuffer.updateFromByteArray這個api,一定是有bug。不論IB里填什么,最終都只能畫出第1個三角形。當時我百思不得騎姐,反復的修改代碼調整數據做各種測試,要知道即使是那么簡單的一個空殼最后鏈接一次都要幾分鍾,那樣子的debug真是叫人吐血啊!最后我實在找不出問題了,但突然想到一個簡單的驗證方法,即去看flascc自帶的sample是否正常,第12個12_stage3d里有兩個例子,第一個是本身就只畫一個三角形,太偷懶了;第二個是SpaceTriangle,名字看起來又是三角形,但看里面的代碼,“ship”發射的"missle"應該是個四邊形(4頂點6索引),可是把它編出來運行后竟然也是——只有三角形!這下我明了了,卻也絕望了,連它自己的例子都不行,那就是說它一次真的只能畫一個三角形了,那還怎么渲染復雜模型啊!當時真有種眼前一黑的感覺,所有努力都白費了,毫無意義啊,更讓我沮喪的是覺得愧對同事K,之前我一直向其宣稱引擎的跨平台特性並聲稱跨到flash上也只是時間問題,導致他對此充滿了希望並將其項目中的核心程序派過來共同研究以求盡早得出成果應用於他的項目。我能做的只有在flascc的論壇上發了帖子報告此bug,然后回家睡覺等回應。雖然大概也相信這肯定只是個bug不可能是其真實特性,但按adobe向來的態度還真不知道它們幾時能改好,而我們就只能一直被吊着了。

  待補……

   12.8 又是兩天過去,對加載圖片有了更多理解,並且也對移植工作流程做了改進,即在C里實現功能之前,先用純as3實現一遍,在flashdevelop里編碼、查找文檔、調試都非常方便,先用as3寫一遍,可以把大致流程梳理清楚,也可以確認想法是否正確,然后再在C里去寫,可以減少很多無謂的排錯時間。關於圖片加載,用as3來做是非常方便的:Loader->URLRequest->BitmapData->Bitmap/Texture;除此外還可以通過Loader.loadBytes直接加載一段現成的字節流。比較奇特的是即使是后者也是異步的,不能馬上得到結果,還是需要在回調中才能使用數據。把這個流程搬到C里,第一種方式正常,第二種卻出錯,總是提示IOError:不認識的文件類型,我在as3和C里都打出了ByteArray的頭幾個字節和長度,都是一樣的,可as3行而C就是不行。這又間接證實了我那個猜測:flascc的ByteArray處理有bug,已經在它論壇上發過2個帖子,可是adobe的工作人員視而不見,幾天前發布了一個依舊的是問題多多的所謂“1.0正式版”后,就放大假似的銷聲匿跡了,實在對他們的工作態度深為不滿。

    接着試驗了用引擎現有的freeimage模塊加載圖片,再把數據傳BitmapData的做法,確認是可行的。但是速度慢得驚人,加載一張256x256的jpg,用時800ms,PNG/TGA分別為500/100ms,而這在“正常平台”下的C程序里用不了1ms。之后用as3加載此jpg做對比,發現只不過30ms,而這還是受限於異步加載所致的最小延遲底限,真實時間可能也不過1ms。之所以相差如此之大,原因也很簡單:as3的類庫自身當然都是用C寫的,是用真正的C編譯器生成了真正的機器碼,而我們的C程序被flascc的“偽C編譯器”編譯后生成的是avm字節碼,當然不在一個檔次上。這也說明了一個道理:入鄉隨俗,既入了flash的戲,就要按它的規矩演。之前一直有種極端的心理,想做一套全功能的包打天下的引擎,然后原封不動移植到各平台,因此初接觸flash時,面對它自身龐大的與我們本身功能重復的類庫時,總是舉棋不定,不知道到底是用它原生的好,還是用我們自己統一的好。現在總算有點醒悟了,之所以想用統一的,無非是因為代碼直接復用(共用),流程不用修改,更重要是有一種“跨平台的虛榮感”;可是缺點很明顯,速度慢,而且功能重復的C代碼也加大了swf的體積,不管怎樣,效率總是第一的,當一種結構導致低效時,無論怎樣優秀的設計都不能彌補這種過失。所以,結果就是,為flash平台的資源加載重做一套流程,對應的也就是能“共用”的邏輯層次又往上提了一段,而被取代的那部份則變成了為適應不同平台而做的不同的接口層。

  12.11 今天adobe人終於活過來了,回了我的貼子解釋了IndexBuffer.updateFromByteArray為什么不行的問題,原來是他們sdk里的sample寫錯了:傳給IB的數據應該是uint16的但是他們寫成了int!結果我是照搬例子代碼的自然也就不行!擦,當初我就感覺不對,索引一般都是用16位的,可是看它寫成int,而純as3里確實也是寫成vector.<int>,也就沒有想過這里會有錯,兀自糾結了半天,卻原來是他們的筆誤,真是吐血啊!今天剛好有兩個同事都問我怎么把flash的ByteArray與C里的指針互轉,這確實是個很關鍵的問題,兩者的數據交互大多靠此,其實在flascc自動生成的as3++ api里,凡是帶ByteArray參數的函數,都會多出一個void*參數來,這個參數就是我們要與之交互的C內存指針,而那個ByteArray參數,則應使用一個特殊的對象:internal::get_ram(),這個對象就是一個表示C里可用內存(俗稱DomainMemory)的特殊ByteArray。

  12.14 周末了。這周本來計划要把一個顯示2d地圖塊的demo做出來,前面進展都還順利,但到最后因為腳本模塊出現詭異bug而告罷。

    首先,搞明白了字體渲染的方法。之前認為要把freetype移植過來,用與其它平台一樣的辦法來產生texture。但經過freeimage的轉變,預先就感到這種辦法可能太慢,應該尋找flash自身的實現,結果在網上搜了下也看了下其它純as3渲染引擎的做法,簡單得令人吃驚:直接把一個textfield畫到bitmapdata上!這不就是我們原來在2d引擎年代常用的技術嗎?每一種對象都能將自己“光柵化”,轉變成一符位圖,其最終顯示在窗口上,也不過是把這個位圖貼到了窗口。可是自從進入GPU時代以來,這種做法逐漸變得陌生,不再普遍到每一個小對象都能單獨“成像”,而是一般只對整個場景、整副界面,在調用某些特殊效果時,才事先渲染到RT。究其原因,還是批次的限制,使小對象的即需即渲變得不再高效。但是flash本身就是個軟渲染引擎,讓它做這種工作,可謂輕車熟路,於是利用textfield來生成字模位圖,就又成了flash模式下的一道特殊風景。我先在純as3里編碼實現了以上思路,但往引擎中整合時,碰到幾個問題:

    1)flash的texture.uploadFromBitmapData不支持設定子區域,必須是全圖對全圖,這對我引擎text對象的設計是個致使打擊,我的做法是一行字生成一張texture,然后將每個字的bitmapdata逐一復制上去,可是在這flascc下沒法實現了;

    2)字庫的加載選擇,也變得與往常迥異,需要大幅調整代碼結構。

    對第1個問題,我數度猶豫,到底是把抽象隔離層做在font上還是text上。如果抽象在font上,只要font能提供每個字符的字模位圖,那應用層無論按何種方式使用都變得平台無關了,但如抽象在text這種實際對象上,那么一旦有對字模位圖的其它需求,就又得為其做兩種實現。然而抽象font的難處就是上面提到的,但是如果換成抽象text則可繞過去。但最終我還是想了個bt的辦法來頂住前者的困難,即一旦texture.upload被以子區域方式調用時,就為其臨時創建一個等大的bitmapdata,之后的所有upload調用都把數據緩存在這個bitmapdata上,直到真正被settexture使用時,才即時上傳到真正的texture上。對第2個問題,其實就是把原來的font和fontmgr兩個類做成抽象接口,原來的實現改名為font_freetype,然后新增一個實現叫font_flash,程序在編譯時即會確定所能獲取到的fontmgr是freetype類型的還是flash類型的,其它模塊如text等在使用時,只使用抽象接口就行了。記得最早的時候font體系是用win32的api做的,后來為了渲染質量換成了freetype,再后來為了跨平台直接去掉了win32 api,現在又增加了flash,真是分分合合,難以盡數。

    今天同事L告知碰到了一個詭異問題,在向lua注冊引擎類時出現了難以解釋的錯誤。這個模塊按理來說是一個純數據邏輯模塊,在往其它平台移植時,它幾乎半點沒有改過就能正常運行,可是偏偏在flash下出錯,真是既奇怪又不奇怪,對於flascc的各種問題,都習以為常了。相信他在反復的調試之后,應該能得出答案。

    

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM