2009年9月3日星期四

Syntax highlight in Notepad++

從互聯網找到個給 Squirrel 語言著色的 Notepad++ config 檔案,也可作為日後 MCore3D Studio 結合 Scintilla Control 的藍圖。把以下的 Xml 儲存為 "userDefineLang.xml" 再放入 Notepad++ 的安置目錄就行了。

<NotepadPlus>
<UserLang name="Squirrel" ext="nut">
<Settings>
<Global caseIgnored="no" />
<TreatAsSymbol comment="yes" commentLine="yes" />
<Prefix words1="no" words2="no" words3="no" words4="no" />
</Settings>
<KeywordLists>
<Keywords name="Delimiters">&quot;00&quot;00</Keywords>
<Keywords name="Folder+"></Keywords>
<Keywords name="Folder-"></Keywords>
<Keywords name="Operators">- ! ( ) , . : ; ? [ ] { } + &lt; = &gt;</Keywords>
<Keywords name="Comment">1/* 2*/ 0//</Keywords>
<Keywords name="Words1">break case catch class clone continue const default delegate delete else enum extends for function if in null resume return switch this throw try typeof parent yield constructor instanceof true false static do while foreach</Keywords>
<Keywords name="Words2">local</Keywords>
<Keywords name="Words3">ARGS</Keywords>
<Keywords name="Words4"></Keywords>
</KeywordLists>
<Styles>
<WordsStyle name="DEFAULT" styleID="11" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="FOLDEROPEN" styleID="12" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="FOLDERCLOSE" styleID="13" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="KEYWORD1" styleID="5" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="1" />
<WordsStyle name="KEYWORD2" styleID="6" fgColor="006262" bgColor="FFFFFF" fontName="" fontStyle="1" />
<WordsStyle name="KEYWORD3" styleID="7" fgColor="007777" bgColor="FFFFFF" fontName="" fontStyle="3" />
<WordsStyle name="KEYWORD4" styleID="8" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="COMMENT" styleID="1" fgColor="008000" bgColor="FFFFFF" fontName="" fontStyle="2" />
<WordsStyle name="COMMENT LINE" styleID="2" fgColor="008000" bgColor="FFFFFF" fontName="" fontStyle="2" />
<WordsStyle name="NUMBER" styleID="4" fgColor="FF8000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="OPERATOR" styleID="10" fgColor="000080" bgColor="FFFFFF" fontName="" fontStyle="1" />
<WordsStyle name="DELIMINER1" styleID="14" fgColor="7E7E7E" bgColor="FFFFFF" fontName="" fontStyle="2" />
<WordsStyle name="DELIMINER2" styleID="15" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="DELIMINER3" styleID="16" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
</Styles>
</UserLang>
</NotepadPlus>

2009年8月29日星期六

Mesa 3D

多年沒有注視的 Mesa 3D 原來已經支援 OpenGL 2.1,亦表示它包含了 Glsl 的編輯器與運行期軟件。但更令我感到驚喜的是它的驅動層是可以使用 Direct3D9 作為實作。這個實作應該是基於 GlDirect 為藍圖,把一個個 OpenGL 指令變換成 Direct3D 的函數。
理想地,我不必再為不同平台間的 Graphics API 而煩惱;因為 OpenGL 本身就是設計成可擴充的,而 Mesa3D 又提供了一個很好的起步點。我的引擎只要沿用 OpenGL,再對 Mesa3D 的驅動層稍為修改就應該可在 XBox 上跑。
這兩天會花點時間對這想法的實際可行性多作研究。

imageimage

2009年8月5日星期三

Restrict 指針

一般時候我們的編譯器都不知道兩個指針所指的位置是否相同或者有所重疊,大大降低了它的優化效果。就拿 Matrix3 * Vector3 為例:

void Matrix::MulVector(const Vec3& v, Vec3& result) {
result.x = m00 * v.x + m01 * v.y + m02 * v.z;
result.y = m10 * v.x + m11 * v.y + m12 * v.z;
result.z = m20 * v.x + m21 * v.y + m22 * v.z;
}

很簡單的三行代碼,然而它隱藏了一個效能上的問題。堂 result 被賦予了新的值後,編譯器認為 v 的值也可能被更改了 (v 和 result 也是 reference,pointer 的一類)。因此原本可以留在 register 裡的 v.x, v.y 和 v.z 被迫從新由記憶體裡閱讀回來。看看VC2008 編譯成的機器碼吧:

result.x = m00 * v.x + m01 * v.y + m02 * v.z;
mov eax,dword ptr [esp+4]
movss xmm0,dword ptr [ecx+8]
mulss xmm0,dword ptr [eax+8]
movss xmm1,dword ptr [ecx+4]
mulss xmm1,dword ptr [eax+4]
mov edx,dword ptr [esp+8]
addss xmm0,xmm1
movss xmm1,dword ptr [eax]
mulss xmm1,dword ptr [ecx]
addss xmm0,xmm1
movss dword ptr [edx],xmm0

result.y = m10 * v.x + m11 * v.y + m12 * v.z;
movss xmm0,dword ptr [ecx+0Ch]
mulss xmm0,dword ptr [eax]
movss xmm1,dword ptr [ecx+14h]
mulss xmm1,dword ptr [eax+8]
addss xmm0,xmm1
movss xmm1,dword ptr [ecx+10h]
mulss xmm1,dword ptr [eax+4]
addss xmm0,xmm1
movss dword ptr [edx+4],xmm0

result.z = m20 * v.x + m21 * v.y + m22 * v.z;
movss xmm0,dword ptr [ecx+18h]
mulss xmm0,dword ptr [eax]
movss xmm1,dword ptr [ecx+20h]
mulss xmm1,dword ptr [eax+8]
addss xmm0,xmm1
movss xmm1,dword ptr [ecx+1Ch]
mulss xmm1,dword ptr [eax+4]
addss xmm0,xmm1
movss dword ptr [edx+8],xmm0

這時候使用 restrict 就可幫上編譯器把。請注意,VC2008 的 __restrict 只對指針生效:

void Matrix::MulVector(const Vec3& v_, Vec3& result_) {
const Vec3* __restrict v = &v_;
Vec3* __restrict ret = &result_;
result->x = m00 * v->x + m01 * v->y + m02 * v->z;
result->y = m10 * v->x + m11 * v->y + m12 * v->z;
result->z = m20 * v->x + m21 * v->y + m22 * v->z;
}

從新編譯後的機器碼:

result->x = m00 * v->x + m01 * v->y + m02 * v->z;
mov eax,dword ptr [esp+4]
movss xmm1,dword ptr [eax+4]
movss xmm0,dword ptr [eax+8]
movss xmm2,dword ptr [eax]
movss xmm3,dword ptr [ecx+4]
movss xmm4,dword ptr [ecx+8]
mov eax,dword ptr [esp+8]
mulss xmm3,xmm1
mulss xmm4,xmm0
addss xmm3,xmm4
movaps xmm4,xmm2
mulss xmm4,dword ptr [ecx]
addss xmm3,xmm4

result->y = m10 * v->x + m11 * v->y + m12 * v->z;
movss xmm4,dword ptr [ecx+10h]
movss dword ptr [eax],xmm3
movss xmm3,dword ptr [ecx+0Ch]
mulss xmm3,xmm2
mulss xmm4,xmm1
addss xmm3,xmm4
movss xmm4,dword ptr [ecx+14h]
mulss xmm4,xmm0
addss xmm3,xmm4
movss dword ptr [eax+4],xmm3

result->z = m20 * v->x + m21 * v->y + m22 * v->z;
movss xmm3,dword ptr [ecx+18h]
mulss xmm3,xmm2
movss xmm2,dword ptr [ecx+1Ch]
mulss xmm2,xmm1
movss xmm1,dword ptr [ecx+20h]
addss xmm3,xmm2
mulss xmm1,xmm0
addss xmm3,xmm1
movss dword ptr [eax+8],xmm3

可以看到記憶體閱讀操作減少了。

到最後,其實使用局部變量也可達到類似效果:

void Matrix::MulVector(const Vec3& v, Vec3& result) {
const float x = v.x;
const float y = v.y;
const float z = v.z;

result.x = m00 * x + m01 * y + m02 * z;
result.y = m10 * x + m11 * y + m12 * z;
result.z = m20 * x + m21 * y + m22 * z;
}

2009年6月21日星期日

引擎開發進度

有了新成員的加盟,遊戲引擎的開發進程加快,以下是一些測試程式的截圖。
  • 漂亮又便宜的 God Ray


  • Cube map + Normal mapping


  • 使用 Bullet 製作的 Physics component


  • 場景編輯器

2009年6月17日星期三

於特定線程設置中斷點

正在開發一個 memory profiler,為了針對多線程部分的除錯,我須要設置一個只對特定線程生效的中斷點。在 Visual Studio 中,有兩個非常類似的方法可達到目的:
  1. 右擊您的 break point 然後使用 break point filter

    選擇線程 id

  2. 另一個方法是使用 break point condition


2009年6月6日星期六

不要被 Windows.h 強暴

引擎的物理部份開始開發,我滿心喜悅地把 Bullet Pyhsics Engine 加入專案,但"咚"的一聲告訴我編譯器掛丟了。誰在作怪呢?當編譯器指向那一向相安無事的數學 max 函數,我就知道是 Bullet include 了 Windows.h。錯就錯在 Bullet 在 btQuickprof.h 哪裡包括了 Windows.h,迫使其他檔案也一併把 Windows.h include 過來,本來潔淨的命名空間就這樣被成千上萬的 macros 如 min, max, CreateWindow, DrawText 等等污染了。
其實 Windows.h 是可以避免在標頭檔中出現的:
  • 盡可能把函數定義由標頭檔移到源碼檔去
  • 大多數基本型別如 DWORD, LARGE_INTEGER 等在標頭檔中可用 unsigned long, __int64 來取代,再在源碼檔用 reinterpret_cast 返回原本的型別。大多數的 struct 指針都可以前置聲明(forward declarate) 如 typedef struct _GUID GUID;
  • 更甚者還會使用 char mMutex[24]; 來取代 CRITICAL_SECTION mMutex; 再加上編譯期 assert 確保兩者內存大小匹配:STATIC_ASSERT(sizeof(mMutex) == sizeof(CRITICAL_SECTION));
把 Windows.h 由標頭檔中抽離以後,不單止命名空間的問題解決了,連編譯的速度都快了哩!

2009年5月30日星期六

Squirrel 單元測試框架

暫時還無法找到一個給予 Squirrel 使用的單元測試框架,唯有自己做吧。Lualuaunit 是一個好的起點,不需一天的時間就把它移到 Squirrel,給它命名為 squnit (下載 / Download)。以下是一些使用範例:

dofile("squnit.nut", true);

class TestToto
{
a = null;
s = null;

function setUp()
{
// Set up tests
a = 1;
s = "hop";
}

function test1_withFailure()
{
print("some stuff test 1\n");
assertEquals(a , 1);
// Will fail
assertEquals(a , 2);
assertEquals(a , 2);
}

function test2_withFailure()
{
print("some stuff test 2\n");
assertEquals(a , 1);
assertEquals(s , "hop");
// Will fail
assertEquals(s , "bof");
assertEquals(s , "bof");
}

function test3()
{
print("some stuff test 3\n");
assertEquals(a , 1);
assertEquals(s , "hop");
assertEquals(typeof a, "integer");
assertClose(0.01, -0.01, 0.02);
}
} // TestToto

class TestTiti
{
a = null;
s = null;

function setUp()
{
a = 1;
s = "hop";
print("TestTiti.setUp\n");
}

function tearDown()
{
// Some tearDown() code if necessary
print("tearDown\n");
}

function test1_withFailure()
{
print("some stuff test 1\n");
assertEquals(a , 1);
// Will fail
assertEquals(a , 2);
assertEquals(a , 2);
}

function test2_withFailure()
{
print("some stuff test 2\n");
assertEquals(a , 1);
assertEquals(s , "hop");
// Will fail
assertEquals(s , "bof");
}

function test3()
{
print("some stuff test 3\n");
assertEquals(a , 1);
assertEquals(s , "hop");
}
} // TestTiti

// Simple test functions that were written previously can be integrated in luaunit too
function test1_withFailure()
{
assert(1 == 1);
// Will fail
assert(1 == 2);
}

function test2_withFailure()
{
assert("a" == "a");
// Will fail
assert("a" == "b");
}

function test3()
{
assert(1 == 1);
assert("a" == "a");
}

TestFunctions <- wrapFunctions("test1_withFailure", "test2_withFailure", "test3");

// SqUnit().run("test2_withFailure"); // Run only one test function
// SqUnit().run("test1_withFailure");
// SqUnit().run("TestToto"); // Run only on test class
// SqUnit().run("TestTiti:test3"); // Run only one test method of a test class
SqUnit().run(); // Run all tests

還可以使用 squnit 本身來測試自己:

/* testSqunit.nut
Description: Tests for the squnit testing framework
Author: Ricky Lung (http://mtlung.blogspot.com/)
Version: 1.0

License: X11 License

This set of files is published under the X11 License. You can do
more or less anything you want to do with it.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE X
CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

// This is a bit tricky since the test uses the features that it tests.

dofile("squnit.nut", true);

class TestSqUnit
{
function mypcall(func, ...)
{
local args = array(0);
for(local i=0; i<vargc; ++i)
args.push(vargv[i]);

try {
func.pacall(args);
return true;
} catch(e) {
return false;
}
}

function testAssertError()
{
local has_error = !mypcall(error, null, "coucou");
assert(has_error == true);
assertError(error, null, "coucou");
has_error = !mypcall(assertError, null, error, null, "coucou");
assert(has_error == false);

local f = function() {}
has_error = !mypcall(f, null);
assert(has_error == false);
has_error = !mypcall(assertError, null, f, null);
assert(has_error == true);

// multiple arguments
local multif = function(a, b, c) {
if(a == b && b == c) return;
error("three arguments not equal");
}

assertError(multif, null, 1, 1, 3);
assertError(multif, null, 1, 3, 1);
assertError(multif, null, 3, 1, 1);

has_error = !mypcall(assertError, null, multif, null, 1, 1, 1);
assert(has_error == true);
}

function testAssertEquals()
{
assertEquals(1, 1);
local has_error = !mypcall(assertEquals, 1, 2);
assert(has_error == true);
}

function testAssertClose()
{
assertClose(1, 1);
assertClose(-1, -1);
assertClose(0.1, -0.1, 0.2);
local has_error = !mypcall(assertEquals, 0.1, -0.1, 0.19);
assert(has_error == true);
}

function XtestXpcall()
{
local f = function() {
error("[this is a normal error]");
}
local g = function(f) {
f();
}
g(f);
}
} // TestSqUnit

//SqUnit().run("TestSqUnit.testAssertEquals"); // Will execute only one test
//SqUnit().run("TestSqUnit"); // Will execute only one class of test
SqUnit().run(); // Will execute all tests