Friday, April 29, 2011

NPAPI & anothor single triangle


不久前看到Insomniac game他們的新工具是利用各種網業技術做出來的,包含html,java script等等,其中有個地方還滿特別的,他們的場景編輯器的3D視窗是利用瀏覽器的plugin方式,直接嵌進瀏覽器中。

於是,我發現了一個長久以來我都視為理所當然,可是一直忽略的事實,那就是瀏覽器應該可以用native code寫plugin吧...flash,quick time,甚至Unity3D引擎,quake live這些技術,看起來不可能是用script寫出來的。

於是我用google查了web browser native plugin或類似的東西,結果就掉出了google html5 native code計畫,html 5大業的確很有潛力,sand box的環境也可能提供比較好的安全性,不過native code計畫看起來還不太成熟,目前只有gcc的環境,而我是個用visual studio的軟派程式員,只好放棄這個方向。不過flash,或unity引擎,都是在html 5的native code計畫出現前就有的東西,顯然有其他的方式可以達成,經過一堆亂七八糟的關鍵字搜尋,終於找到了關鍵字是甚麼---NPAPI和ActiveX。

NPAPI是給非IE用的瀏覽器plugin API(Firefox,Chrome等等),ActiveX就是...恩,微軟的IE。這兩個API,都可以讓我們在瀏覽器中,用C/C++寫plugin。另外這也是為甚麼plugin都分IE和非IE兩種...

有關NPAPI的資訊,其實在網路上還滿少的,官方文件和這個blog,我覺得算是比較好的資源。其中那位blog的作者,開發了FireBreath這套可以跨NPAPI和ActiveX的plugin API。另外在codeproject上有一個範例

因為我只想用OpenGL畫一個三角形,於是我下載了codeproject上的範例修改了一下,把有關scriptable的部分全部拿掉,然後就畫了一個三角形。

真的有興趣的人,可以用svn checkout http://ianjoker.googlecode.com/svn/trunk/npgl下載source code。

稍微紀錄一下NPAPI的設計,首先,他是"真正"的plugin設計,compile好的NPAPI plugin是一個動態函示庫(.dll或.so),而且,這個plugin理論上可以給FireFox,Chrome,Safari或任何支援NPAPI的瀏覽器使用,而不用重新compile或link任何東西。這與一些不太plugin架構的plugin很不一樣,例如Ogre引擎中的RenderSystem或SceneManager等plugin,只要OgreMain有改變重新compile,這些plugin就必須重新compile或link OgreMain.lib,否則不保證plugin可以用。

可是NPAPI plugin不用重新link卻可以給各個瀏覽器(還包括不同版本,不同compiler等)使用,這是怎麼辦到的呢?好吧,原因很簡單,因為一開始就根本不用link任何.lib。

NPAPI靠的,是用C語言的ABI(Application binary interface)非常統一的特性,幾乎所有的interface都使用C語言制定,其中有三個C的inteface是瀏覽器與plugin的接口。

NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs);
NPError OSCALL NP_Initialize(NPNetscapeFuncs* bFuncs);
NPError OSCALL NP_Shutdown();


C的interface,可以利用GetProcAddress(windows)或dlsym(Linux)來取得位址。因此瀏覽器不用去link任何plugin的lib就可以呼叫這三個function。這當然是任何plugin設計的基礎,但是還缺了一半,如果plugin不link瀏覽器的.lib,那plugin就無法呼叫任何瀏覽器本身的function,瀏覽器無法提供任何服務給plugin使用!

解決方案很簡單,NP_Initialize這個function會在plugin被瀏覽器載入時呼叫,這時候會傳進來一個NPnetscapeFuncs,這其實是一個function pointer的table,裡面每個function pointer就是瀏覽器提供給plugin呼叫的function,例如我想跟瀏覽器alloc一塊記憶體,我可以呼叫NPnetscapeFuncs::memalloc。因此plugin不用link瀏覽器的.lib,link這件事情,是在runtime執行NP_Initialize的時候做的。

反向的則是NP_GetEntryPoints會需要我們填function pointer進NPPluginFuncs這個function pointer table,這些function pointers是plugin提供給瀏覽器呼叫的callback,例如當瀏覽器需要new一個新的plugin的instance的時候,瀏覽器會呼叫NPPluginFuncs::newp這個callback,我們必須實做這些function,然後把他們填入NPPluginFuncs交給瀏覽器。

因為我想做的是利用OpenGL畫三角形,想要OpenGL就必須要有window handle,而NPPluginFuncs正好有提供setwindow這個callback,會在瀏覽器建plugin window的時候呼叫,瀏覽器呼叫這個callback的時候,會傳進一個NPWindow struct,這個struct可以拿到window handle!任何一個graphics programmer,只要能拿到window handle就可以解決一切難題!!!

當然,事情永遠不會這麼簡單的...

首先是API的header問題,雖然說理論上這是一個跨瀏覽器的API,包含了npapi.h,npfunctions.h,npruntime.h,nptypes.h四個檔案,不過只要有人的地方,就有紛爭,更不用說瀏覽器這種兵家必爭之地了。我在demo中放了Mozilla的官方SDK版本。我試過在FireFox 3.x或4.x都是ok的,不過很可惜的是Chrome似乎不能用。Google他們自己可能也看到了這個問題,因此他們maintain了一份號稱可以跨平台的header,可惜的是我試過似乎在FireFox下會當掉。我還沒有仔細檢查哪裡出了問題,不過如果真的要寫跨平台的plugin,說不定比較好的方式還是去用FireBreath那套API比較好,否則要自己去處理一些跨瀏覽器的問題。


另外是plugin安裝的方式,按照Mozilla官方文件說法,在windows平台上要去registry註冊一些機碼好讓瀏覽器可以認得plugin放的地方,application的MIMEType,附檔名等資訊等等。不過我試了老半天沒辦法work,去看了codeproject的demo做法是直接用.rc檔內嵌在編譯出來的.dll中了,然後必須把編譯好的.dll丟到(FireFox安裝資料夾)\plugins\這個資料夾下面,如果沒有這個資料夾就自己建一個。當然這好像不是理想的做法,因為plugin無法安裝到我們自己想安裝的地方。另外還有個小地方也很奇怪,那個.rc檔裡面有個語言的選項,因為我的VC是中文版的,所以他預設都設成中文,可是中文的plugin無法用,一定要改成英文才行,這裡也是完全不知所以然

最後,因為plugin畫面更新是lazy的方式,瀏覽器覺得需要更新那個window才會有windows message重畫那個window,當然這樣做無法做real time的遊戲,如果要自己控制更新window,只好自己開另一個thread來建OpenGL context。總之,這只是implementation detail,好讓我可以得到60 FPS的triangle。60 FPS就是比較帥(自high)~

結論是,雖然這不是甚麼很難的技術,不過感覺網路上資源還滿少的,如果真的想要把這鬼東西放進工具,可能很多小細節都會讓實作的人很痛苦吧,而且那個痛苦的人看起來就是我啊...


題外話,IE9的javascript支援真是爛到爆了,編個blogger結果無法存檔也無法publish,而且這已經不是我第一次犯這個錯了。

Saturday, March 19, 2011

麥田捕手

"如果對社會有不滿,就改變自己吧,如果不想改變自己,就把耳朵,眼睛,嘴巴閉上,孤獨地過日子吧" -- 草薙素子

"I Thought what I'd do was, I'd pretend I was one of those deaf-mutes."

"一個不成熟的人的特徵,是他願意為某種信念英勇的死去;一個成熟的人的特徵,是他願意為某種信念謙卑地活著。"

看了攻殼機動隊,所以去找了這本名著來看。

Tuesday, February 15, 2011

OpenGL extensions & code generator

Yeah, OpenGL extensions sucks, but we have to live with it.

To get extensions work on different platforms often means various mechanisms to get the extensions' function pointer, deal with different calling conventions, etc.

The easiest way to deal with such mess is use a library like GLEW and forget about it. But sometimes when I code my own little stuff, I only use a very small set of extensions(often times I just want to test the extensions functionality) , GLEW's mega-header is often very browse/search unfriendly, and if the GLEW's version is out of date, I have to download it again.

I used to code every single extension manually, and it's really boring and repeated work. I don't know if there exist an automatic extensions generator(I know it exist, most extensions lib is generated by such generator, I just have to find an excuse to make my own), give it a file which contain the function definition, and it output the .h and .cpp. Then I put the two files in my project and voila.

I think this is an interesting little project and recently I have been learning Python. So I give it a try using Python.

The gl extension source file looks like this

[core]
void glEnable (GLenum capability)
void glDisable (GLenum capability)
void glBlendFunc (GLenum sfactor, GLenum dfactor)
...

[GL_EXT_texture_compression_s3tc]
...

The generator read each line of the file, then using Python's regular expression module re to parse the line to see if it's an [...], which means a group of extensions. [core] group is special, which contains functions in core GL spec . Other [...] must contains it's extension's name string. And if the line is parsed as function declaration, it is split into three pieces - return type, function name and args list, then it's stored in a Python dictionary using function name as key for latter use.

When parse complete, the generator output .h and .cpp. The .h look like this(with a lot of hassle omitted),

//core
extern void(JGL_API_ENTRY jglEnable) (GLenum capability);
extern void(JGL_API_ENTRY jglDisable) (GLenum capability);
extern void(JGL_API_ENTRY jglBlendFunc) (GLenum sfactor, GLenum dfactor);
...
//GL_EXT_texture_compression_s3tc
...

namespace joker
{

struct glCaps{
bool support_core;
bool support_GL_EXT_texture_compression_s3tc;
};

void InitGL(glCaps** caps);
}

then in my project, I just call InitGL(&caps), init the extensions and get the caps, if the extension is supported, I can call it's jglXXX function.

the generator is halfway done, the caps check is not implemented yet, and I also want to add log version of gl functions. And it only support Mac/Windows.

The source can be found here. and the extension def file here.

Yeah, many stuff looks like Quake 3's qgl, because I steal most ideas from it :) .

And of course this is only a toy, any production code should use GLEW instead.

P.S. Now I am really confused, what's the difference between core/extension? OpenGL sucks...