|
|
11月26日
nViewerApp::Open() { // 创建d3d9和Gui服务: 一。this->refGfxServer = (nGfxServer2*) kernelServer->New(this->GetGfxServerClass(), "/sys/servers/gfx"); this->refGuiServer = (nGuiServer*) kernelServer->New("nguiserver", "/sys/servers/gui");
// 打开显示: 二。this->refGfxServer->OpenDisplay() // refGfxServer is nD3D9Server { // d3d9->CreateDevice并且InitDeviceState( Restore device and NewShader("shape") && NewShader("shared") and // SetCamera(this->GetCamera()) that refresh project matrix 1. DeviceOpen(); // 2. nD3D9Server.BeginScene(){ nGfxServer2::BeginScene() // ClearLights()和inBeginScene = true d3d9Device->BeginScene(); UpdateSharedShaderParams(); // share shader设置time and disRes } 3. Clear(AllBuffers, 0.0f, 0.0f, 0.0f, 1.0f, 1.0, 0); 4. EndScene(); // d3d9Device->EndScene() then nGfxServer2::EndScene()(inBeginScene = false) 5. PresentScene(); // d3d9Device->Present(0, 0, 0, 0) then nGfxServer2::PresentScene()(n_assert(!this->inBeginScene);) }
三。输入服务: this->refInputServer = (nInputServer*) kernelServer->New("ndi8server", "/sys/servers/input"); this->DefineInputMapping();
四。创建场景根结点: kernelServer->New("ntransformnode", "/usr/scene");
五。RenderPath: this->refSceneServer->Open()); // 通过renderPath.Open()调用ParseXmlFile()来装入Pass.Phase.Sequence到passArray中
六。初始Gui: this->refGuiServer->SetRootPath("/gui"); this->refGuiServer->SetDisplaySize(vector2(float(this->displayMode.GetWidth()), float(this->displayMode.GetHeight()))); this->refGuiServer->Open();
}
nebula2流程
第一章: 初始 一。产生nebula核心对象: nKernelServer kernelServer; 在其构造函数中: // 1. 加载核心包: // AddPackage功能,将类名及产生该类的方法加入到kernelServer的classlist中,以便后面可用名字来new出实例 this->AddPackage(nkernel); nkernel包中的对象有: nobject nroot nenv nfilenode nfileserver2 npersistserver nremoteserver nscriptserver nsignalserver ntimeserver
// 2. 创建根"/"对象: this->root = (nRoot *) this->NewUnnamedObject("nroot"); n_assert(this->root); this->root->SetName("/"); this->cwd = this->root;
// 3. 创建核心服务: this->fileServer = (nFileServer2*) this->New("nfileserver2", "/sys/servers/file2"); this->persistServer = (nPersistServer*) this->New("npersistserver", "/sys/servers/persist"); this->remoteServer = (nRemoteServer*) this->New("nremoteserver", "/sys/servers/remote"); this->timeServer = (nTimeServer*) this->New("ntimeserver", "/sys/servers/time");
二。用核心对象来加截包: 将类名及产生该类的方法加入到kernelServer的classlist中,以便后面可用名字来new出实例 kernelServer.AddPackage(nnebula); kernelServer.AddPackage(ndinput8); kernelServer.AddPackage(ndirect3d9); 例如第一行就调用了nnebula,此函数进行了如下处理: void nnebula() { nKernelServer::ks->AddModule("nanimationserver", n_init_nanimationserver, n_new_nanimationserver); nKernelServer::ks->AddModule("naudioserver3", n_init_naudioserver3, n_new_naudioserver3); // ...... }
三。用包中类名产生实例: 在bool nViewerApp::Open()中: // initialize Nebula servers this->refScriptServer = (nScriptServer*) kernelServer->New(this->GetScriptServerClass(), "/sys/servers/script"); this->refGfxServer = (nGfxServer2*) kernelServer->New(this->GetGfxServerClass(), "/sys/servers/gfx"); this->refSceneServer = (nSceneServer*) kernelServer->New(this->GetSceneServerClass(), "/sys/servers/scene"); // ......
四。运行startup.tcl脚本: 在bool nViewerApp::Open()中: 1. 程序先用file2服务设置proj 及 home 的路径 2. 运行startup.tcl脚本 3. 调用该脚本中的"OnStartup"及"OnGraphicsStartup"函数 a. 在OnStartup函数中用file2设置data及scripts等路径 b. 在OnGraphicsStartup函数中根据/sys/servers/gfx.getfeatureset来设置renderpath: 1) "dx9": DX9 hardware without floating point render targets /sys/servers/scene.setrenderpathfilename "data:shaders/dx9_renderpath.xml" 2) "dx9flt": DX9 hardware with floating point render targets, use HDR render path /sys/servers/scene.setrenderpathfilename "data:shaders/dx9hdr_renderpath.xml" 3) 其它, non-DX9 hardware, use fixed function render path /sys/servers/scene.setrenderpathfilename "data:shaders/dx7_renderpath.xml"
五。最后是图形初始: 在bool nViewerApp::Open()中: 1. 设置图形接着所用照相机: this->refGfxServer->SetCamera(this->camera); // camera是一个nCamera2对象,它是nViewerApp的成员 2. 接着初始场景, 打开对应的renderpath.xml: this->refSceneServer->Open(); 这会调用nSceneServer::Open()来打开前面在第四。3.b中所设置的renderpath(即一个xml文件) (即通过renderPath.Open()调用ParseXmlFile()来装入Pass.Phase.Sequence(包括.fx文件名)到passArray中) nSceneServer::Open()内容: RenderPath的处理: nSceneServer下有一个nRenderPath2对象:renderPath renderPath下有一个xmlParser对象 该xmlParser对象有一个nRenderPath2指针指向对应上述的renderPath 当xmlParser分析renderPath时会 生成renderPath->listPass 每个Pass->listPhase 每个Phase->listSequence 每个Sequence对应一个.fx文件
3. 初始按键映射: this->refInputServer = (nInputServer*) kernelServer->New("ndi8server", "/sys/servers/input");
4. 创建"/usr/scene"对象: kernelServer->New("ntransformnode", "/usr/scene");
5. 初始UI: this->refGuiServer->SetRootPath("/gui"); this->refGuiServer->SetDisplaySize(vector2(float(this->displayMode.GetWidth()), float(this->displayMode.GetHeight()))); this->refGuiServer->Open(); if (this->isOverlayEnabled) { this->InitOverlayGui(); }
6. // set the stage and load the object, 由于在前面没有传参数进来通过nViewerApp::SetSceneFile来设置场景文件, 所以详细我们放到后面加载场景时再说。
7. 初始视矩阵
第二章: 加载场景
nGuiServer::Trigger() 中调用 focusWindow->OnButtonUp(mousePos); focusWindow是从nGuiWidget派生的实例,它会基类nGuiWidget::OnButtonUp(const vector2& mousePos)事件, 其中: 1. 将本实例的事件传给ui server单键进行处理:nGuiServer::Instance()->PutEvent(this->event); 2. 然后再来遍历所有子控件并调用它的OnButtonUp事件(而子控件又是从基类nGuiWidget派生的,故也会处理其子控件)。
说明: 在nGuiServer::Instance()->PutEvent(this->event)时,会遍成nGuiServer下的所有事件监听者(链表)处理: eventListeners[i]->OnEvent(event); 而如果在Nebula2的装载图象文件(*.n2)时就会调用到nGuiGraphicsBrowserWindow这个监听者进行处理: nGuiGraphicsBrowserWindow::OnEvent(const nGuiEvent& event) 其中会通过nGuiGraphicsBrowserWindow::LoadObject(const nString& objPath)来装入*.n2文件.
nebula渲染流程伪代码如下: void nKylinGame::Render(float dt) { 1. 检查表面丢失并开始: 调用 gfxServer->BeginScene()处理testResetDevice() 并调用this->d3d8Dev->BeginScene()
2. 清屏: gfxServer->ClearScreen(); 即调用d3d8Dev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, this->d3dClearColor, 1.0f, this->stencilClear);
3. 创建场景根结点并设置矩阵: nSceneGraph2::BeginScene(const matrix44& invViewer) 创建"root node" 并设置其transform to the inverse viewer matrix: this->BeginNode(); this->SetTransform(invViewer);
4. 把渲染场景中各结点加入渲染池中: this->sceneGraph->Attach(renderScene.get(), 0); 于是在nSceneGraph2::Attach(nVisNode* visNode, int renderContext)中visNode->Attach(this) n3DNode::Attach(nSceneGraph2* sceneGraph) { sceneGraph->BeginNode() // 开始处理新的一个池结点
// 1.处理本n3DNode的置换矩阵: sceneGraph->AttachTransformNode(this); { node->Compute(this){ sceneGraph->SetTransform(resMatrix); // 包括billboard、sprite等处理 即设到当前池中:nodePool[this->curNode].matrix = matrix; } } // 2.处理所有子结点: retval = nVisNode::Attach(sceneGraph); // 如果子结点还是n3DNode的话则递归继续重入该Attach函数 // 对于其它类型的结点,则先处理所有子结点,再处理自身的Attach, // 例如子结点为nSoundNode时,选调用nVisNode::Attach(sceneGraph)来处理子结点, // 然后调用sceneGraph->AttachSoundNode(this);把自己加入当前池中: // this->nodePool[this->curNode].soundNode = node;
sceneGraph->EndNode(); // 退回上一个池结点 }
5. 结束时将结点归类并渲染: sceneGraph->EndScene(true){ a. TransformAndSplitNodes 将池中结点分成三大类:geometry/light/sound b.render lights(渲染灯光): this->RenderLights(gfxServ, shadowServ); c.RenderSounds(audioServ)(渲染声音)
// 而对于geometry,为了减少渲染状态的切换,先排序再渲染: d. SortGeoNodes(); 对geometry进行排序,顺序是: -# render priority -# opacity -# texture -# pixel shader -# vertex shader // 以下是两步(e和f)是公司同事加入的,原nebula到此直接渲染了 e. SplitNodeByLight, 对geometry根据灯光分成如下几类(见如下的渲染) f.最后按灯光类型渲染: this->RenderNode( this->renderNodes_NoLight, this->numNoLightNodes, VS_LIGHTINDEX_NOLIGHT, nSceneGraph2::RENDER_OPAQUE ); this->RenderNode( this->renderNodes_Ambient, this->numAmbientNodes, VS_LIGHTINDEX_AMBIENT, nSceneGraph2::RENDER_OPAQUE ); this->RenderNode( this->renderNodes_AmbientIns, this->numAmbientInsNodes, VS_LIGHTINDEX_AMBIENTINSIDE, nSceneGraph2::RENDER_OPAQUE ); this->RenderNode( this->renderNodes_DirLight, this->numDirLightNodes, VS_LIGHTINDEX_DIRLIGHT, nSceneGraph2::RENDER_OPAQUE ); this->RenderNode( this->renderNodes_PointLight1, this->numPointLight1Nodes, VS_LIGHTINDEX_POINTLIGHT1, nSceneGraph2::RENDER_OPAQUE ); this->RenderNode( this->renderNodes_PointLight2, this->numPointLight2Nodes, VS_LIGHTINDEX_POINTLIGHT2, nSceneGraph2::RENDER_OPAQUE ); this->RenderNode( this->renderNodes_Alpha, this->numAlphaNodes, 0, nSceneGraph2::RENDER_ALPHA ); #endif } 而具体在 nSceneGraph2::RenderNode处理中: // 遍历处理所有此类型结点 for ( int i = 0 ; i < count ; i++ ) { a. // compute current state of pixel shader if (node->shaderNode) { nVisNode* shaderNode = node->shaderNode; node->pixelShader = 0; shaderNode->Compute(this); } b. // compute current state of mesh and optionally shadow caster if (node->meshNode) { nVisNode* meshNode = node->meshNode; node->vertexBuffer = 0; meshNode->Compute(this); } c. 设置矩阵: // set modelview matrix for this node gfxServ->SetMatrix(N_MXM_MODELVIEW, node->matrix);
d. // render mesh if (node->vertexBuffer) { // make sure we don't render any ReadOnly vertex buffers n_assert(N_VBTYPE_READONLY != node->vertexBuffer->GetVBufType()); if ( hvs != NULL ) { if ( type == nSceneGraph2::RENDER_ALPHA ) hvs->SetCurVertexShaderUsage(node->vsLightFlag); else if ( type == nSceneGraph2::RENDER_OPAQUE ) hvs->SetCurVertexShaderUsage(litflag); } node->vertexBuffer->Render(node->indexBuffer, node->pixelShader, node->texArray, hvs ); } } }
nD3D8PixelShader 一。外部通过类似:if (ps) numPasses = ps->BeginRender(ta);方式来开始nD3D8PixelShader处理 在BeginRender(nTextureArray *ta)函数中: 在其中首先保存了this->tarray = ta,其次主要是调用了emit_pass函数来将各pass的状态设置 保存在state_block[i]中(通过BeginStateBlock来实现), 该函数分三步走: 1.emit_simple_states: 这个函数设置了一些与贴图无关的渲染选项(在d3d中主要是调用SetRenderState来设置实现的) 2.emit_texture_states: 设置了贴图相关的操作 a. texture address (如: D3DTADDRESS_WRAP等) b. min or mag filter (如: D3DTEXF_POINT等) c. texture coordinate source (如: D3DTSS_TCI_CAMERASPACEPOSITION等) 3. emit_blend_op: 处理贴图混合基台, 如: dev->SetTextureStageState(stage, D3DTSS_ALPHAARG1, arg2d3d(d3d_alpha_arg0));
二。接着会循环numPasses次调用ps->Render(currentPass): 在nD3D8PixelShader::Render(int pass)函数中: 1。先是应用了前面所保存的对应pass状态:dev->ApplyStateBlock(this->state_block[pass]); 并在第1次pass时, set fog state。 2。接着通过this->set_texture_objects(gs,pass);来设置贴图 3。在第1次pass时设置材质属性: dev->SetMaterial(&d3d8_mat);包括dev->SetRenderState(D3DRS_TEXTUREFACTOR,const_color); 4。最后设置uv矩阵 11月25日 Nebula引擎技术讲解1 : 网站好久没更新了,现在还是写点东西吧: [说明] Nebula是德国一套开源的3d引擎,具体怎样这里不作评价,相关资料可从网上搜索. 官方网站参见: http://www.radonlabs.de使用Nebula也算蛮久了, 为了给网站充数点内容,现在来说说它所用到的一些c++相关技术
如何实现一套与脚本紧密联系的对象管理系统(将在后面给出自己仿照nebula而写的完整可运行的程序): 一.基本的思想: 1. 程序中所产生的对象用类似于windows操作系统中的目录结构来管理, 也就是每个对象是一个结点(有对应的名称),每个结点下可以有多个子结点(也有各自对应的名称).如此循环...
2. 程序中有了以上的"目录结构式的对象管理系统", 脚本可通过与程序结合来实现在脚本中调用相应方法来创建 各目录及对象,并可在脚本中通过相应的目录结构名称来访问每一个对象,以及在各对象之间传递消息.当然也可以通过目录 结构名来删除对象(包括其目录下的子对象也会自动删除).
4. 有了以上的一切就有了一套与脚本紧密联系的对象管理系统.下面给出一个使用tcl脚本来撰写的例子参考如下 (这个例子只供参考,具体可实际编译运行通过的例子将在后面详解后给出): #-------------------------------------------------------------------------- # 创建: new nd3d8server /sys/servers/gfx # 访问: sel /sys/servers/gfx .setdisplaymode "-type(win)-w(800)-h(600)" .setclearcolor 0.0 0.0 0.0 0 .setperspective 45 1.33 1.0 10000.0 sel .. # 删除: delete /sys/servers/gfx #-------------------------------------------------------------------------- [说明:] 前面带"#"号的行表示注释. new 是nebula实现的方法, 可以根据给出的字符串参数产生对应的类型对象 sel 是nebula实现的方法, 可以根据给出的字符串参数选择对应的类型对象, 当参数为".."时表示退到上一层目录 二.具体实现: 首先我们分析要实现如上的脚本目录对象系统所需要的技术: 1. 程序中要实现目录结构的对象管理系统. 2. 要有一套脚本系统,这可以用现成开源的.这里我们选择nebula习惯所用的tcl(当然你可以选择其它) 3. 程序与脚本结合(交互).
下面我们先分别讨论一下nebula是如何如何实现上述各点的: --> 1.先看如何实现第1点: a. 首先nebula有一个叫nRoot的类,它是所有对象类的基类,nRoot中有一个名字变量,有一个parent指针指向它的父结点, 还有一个childlist链表保存它所拥有的子结点.因为结点也是nRoot类,所有结点的子结点中也可能有子结点, 这样就形成了一个树形目录结构.nRoot中有一个nRoot* Find( const char* name )方法,功能很简单,就是遍历自已的 childlist链表对比每一个nRoot结点对象的名字,如果相同就返回些对象的指针.
b. 然后nebula还有一个叫nKernelServer的核心管理类, 1) 一开始nKernelServer就产生了一个全局可见的单件对象(单件就是只有一件,也就是全局唯一,你可以把它想象成一个全局的静态对象) 2) 这个nKernelServer对象有一个根据类名字符串来创建对象的方法: CheckCreatePath( const char* szClassName, const char *szPath ); 第一个参数是类名字符串,第2个参数就是目录对象名.例如你有一个图形管理类:nGfxServer,你可以创建它的一个对象并起名为"/sys/servers/gfx", 如: ks->CheckCreatePath( "nGfxServer", "/sys/servers/gfx" );[注:ks即nKernelServer的单件对象]因为传的是类名字符串,所以从脚本中写下: new nGfxServer /sys/servers/gfx 这样的语句时,tcl会把"nGfxServer"字符串返回给nebula的tclcmd_New函数(这个函数是nebula在初始tcl时注册告知tcl的), 然后tclcmd_New通过调用单件ks的CheckCreatePath( "nGfxServer", "/sys/servers/gfx" );方法来创建了这么一个对象. 3) 另外nKernelServer中有一个nRoot* Lookup( const char* szPath )的函数, 功能是你传入目录对象名,它会查到并返回对应对象指针. 4) 当然nKernelServer中还有一个nRoot *cwd变量, 它是用来指向当前目录(或是对象)的,例如,你在脚本中写下: sel /sys/servers/gfx 这样的语句时,tcl分析到sel时就调用nebula告诉它的tclcmd_Sel函数(如何注册函数告知tcl这是我们在后面第2点的内容), 而tclcmd_Sel函数 会调用上述所说ks->Lookup函数来找到"/sys/servers/gfx"对象的地址,并把它赋给nKernelServer的cwd变量,以后,写下: .setdisplaymode "-type(win)-w(800)-h(600)" .setclearcolor 0.0 0.0 0.0 0 .setperspective 45 1.33 1.0 10000.0 [注意:前面带"."号] 这时,tcl脚本没有这样的用法,于是它就把这些字符串传给一个叫tclcmd_Unknown的函数,nebula在其中分析字符串,把"."与后面的字符串一一分开, 并把"."后的第一个字符串(如:"setdisplaymode")作为nKernelServer的cwd所指向的对象的方法来调用,并传给它接着后面的参数 (如:"-type(win)-w(800)-h(600)").当然要让cwd对象把"setdisplaymode"字符串当成它的函数来调用是比较麻烦的.nebula是如何实现的呢? 我们将在后面第3点祥细说明:), 说了这么多,现在还是让我们来看看一个实现上述目录对象管理系统的代码: [注: 这是我仿nebula写的简单程序,不考虑效率等问题,只作说明用]
// tnd.cpp : Defines the entry point for the console application. //
#include <vector> #include <map> #include <string> #pragma warning(disable: 4786) using namespace std;
#ifndef MAX_PATH # define MAX_PATH 1024 #endif //---------------------------------------------------------------- class nRoot { string m_strName; nRoot *m_pParent; vector<nRoot*> m_vecChild; public: nRoot(const char *szName) : m_strName(szName), m_pParent(0){} nRoot* Find( const char *szName ){ for( unsigned int i=0; i<m_vecChild.size(); i++ ){ nRoot *pRoot = m_vecChild[i]; if( 0 == pRoot->m_strName.compare( szName ) ){ return m_vecChild[i]; } } return 0; } void SetName( const char *szName ){ m_strName = szName; } void AddChild( nRoot *pChild ){ m_vecChild.push_back( pChild ); } }; //---------------------------------------------------------------- class nGfxServer : public nRoot { public: nGfxServer(const char *szName) : nRoot(szName){} void Test(){ __asm int 3 // 这里是测试看是否到这里中断(vc下支持的写法,其它编译器请用类似std::cout方法代替) } }; //---------------------------------------------------------------- class nKernelServer { nRoot *m_pRoot; nRoot *m_pCWD; nRoot* NewObject( const char *szClassName, const char *szName ); public: nKernelServer(){ this->m_pRoot = new nRoot("/"); this->m_pCWD = this->m_pRoot; } nRoot* CheckCreatePath( const char* szClassName, const char *szPath ); nRoot* Lookup( const char* szPath ); void SetCwd( nRoot *o ){ this->m_pCWD = o ? o : this->m_pRoot; } }; static nKernelServer g_ks; //---------------------------------------------------------------- //---------------------------------------------------------------- nRoot* nKernelServer::CheckCreatePath( const char* szClassName, const char *szPath ) { nRoot* parent = 0; nRoot* child = 0; if( '/' == szPath[0] ){ // AbsolutePath parent = this->m_pRoot; }else{ parent = this->m_pCWD; } char strBuf[MAX_PATH]; strcpy(strBuf, szPath); char *pNextPathComponent = NULL; char *pCurrPathComponent = strtok(strBuf, "/"); if( pCurrPathComponent ) { // for each directory path component while( (pNextPathComponent = strtok(NULL, "/") )) { child = parent->Find( pCurrPathComponent ); if (!child) { child = new nRoot( pCurrPathComponent ); parent->AddChild( child ); } parent = child; pCurrPathComponent = pNextPathComponent; } } // curPathComponent is now name of last path component child = parent->Find( pCurrPathComponent ); if (!child) { child = (nRoot*) this->NewObject( szClassName, pCurrPathComponent ); parent->AddChild( child ); } return child; } //---------------------------------------------------------------- nRoot* nKernelServer::Lookup( const char *szPath ) { nRoot* cur = 0;
// check for empty string if( szPath[0] ) { char* nextPathComponent; char strBuf[MAX_PATH];
// copy path to scratch buffer char *str = strBuf; strcpy( strBuf, szPath ); if ( '/' == strBuf[0] ) { cur = this->m_pRoot; } else { cur = this->m_pCWD; }
while ((nextPathComponent = strtok(str, "/")) && cur) { if( str ){ str = 0; } cur = cur->Find(nextPathComponent); } } return cur; } //---------------------------------------------------------------- nRoot* nKernelServer::NewObject( const char *szClassName, const char *szName ) { // 如下创建方法是不可取的,这里仅仅为了说明而作的简单测试: // 稍后我会给出一种更好的方法来作处理,当然nebula实际细节不是这么作的, // 它的做法是产生一个nClass对象,此nClass有对应类的创建和初始化函数指针,并有 // 一个cmdlist链表来记录对应类的脚本函数指针. if( 0 == stricmp( "nGfxServer", szClassName ) ){ return new nGfxServer( szName ); } // ... return 0; }
//---------------------------------------------------------------- int main(int argc, char* argv[]) { char gfxPath[] = "/sys/servers/gfx"; nGfxServer *pGfx = (nGfxServer*)g_ks.CheckCreatePath( "nGfxServer", gfxPath ); if( pGfx ){ pGfx->Test(); } pGfx = (nGfxServer*)g_ks.Lookup( gfxPath ); if( pGfx ){ pGfx->Test(); } return 0; }
[说明] 以上所作方法与nebula实现方法是不一样的,这里仅作说明用,而nebula是实现了一个双向链表, 代码少,效率高. 双向链表用到了一些编程技巧,虽然简单,不过相信只有你对c++指针有一定的了解才真正看得懂, 如果你有兴趣具体可以参见nNode及nList.
--> 2.现在让我们来看如何实现第2点:
Nebula2中的矩阵定义解释
Nebula2中的矩阵定义解释: shared float4x4 Model; // the model matrix shared float4x4 View; // the view matrix
shared float4x4 Projection; // the projection matrix shared float4x4 ModelView; // the model * view matrix shared float4x4 ModelViewProjection; // the model * view * projection matrix shared float4x4 InvModel; // the inverse model matrix shared float4x4 InvView; // the inverse view matrix shared float4x4 InvModelView; // the inverse model * view matrix shared float4x4 ModelLightProjection; // the model * light * projection matrix
如果发现上述解释难以理解,那么对照于以下ogl相关的矩阵解释你就明白了: World Space = Mul(Model Matrix, Object Space) Eye Space(View) = Mul(View Matrix, World Space) = Mul(ModelView Matrix, Object Space) Clip Space = Mul(Projection Matrix, Eye Space) = Mul(ModelViewProjection Matrix, Object Space)
flipcode 2005-8-25 12:16:041. ks->root下记录了所有对象及对应名称,每个对象关联到各自对应的类模板。 2. ks->classlist下记录了所有类模板。 3. 而每class->cmdTable列表记录了该类所拥有的cmd(每个cmd带有静态脚本函 数的名字、id、地址相关信息) 4. 并且每个类模板记录有该类的初始函数、创建函数、公开脚本函数的函数地址。 5. ks->AddModule(类模板名称,初始函数,创建函数);可创建一个类模板实例。如: ks->AddModule("nroot", n_init_nroot, n_new_nroot); // 类模板构造自动记录初始函数n_init及创建函数指针n_new,并立即调用初始 // 函数来记录类名及类大小并呼叫公开脚本函数的函数n_initcmds来公开函数供脚本调用。 5. ks->New(类模板名称, 对象名称);可创建一个对象。如: ks->New("nfileserver2", "/sys/servers/file2"); // ks查找出相关类模板,用它所记录的n_new函数指针来new出此目录结构的 // 每一个对象并赋予它对应名称同时呼叫它的initialize()函数(注:不同于n_init)。
// 因为这里对象是通过名称创建,同样脚本也就可以创建出所要的对象了。 // 当我们要调用一个对象的函数时,ks从它的root下的层次目录按名字找出该对象, // 然后通过对象找到关联的类模板,再用函数名字查询类模板的cmdTable来找到相应 // 的静态函数地址,调用并传入此对象地址,这样就可实现脚本创建对象及调用函数了。 Nebula的智能指针:
用nref<T> refA = A;来引用对象时 会通知在A来让A->reflist表中记录引用者refA。 当A删除时会遍历reflist表来告知本对象已无效, 此时refA->Invalidate();
autoref<T> 派生自nref<T>,功能类似,多了个引用者"名字"常量, 当引用的对象不存在时会自动调用kernelserver的 静态变量ks来按"类名"产生相应类的实例。 (按类名产生相应类实例请参见nclass.txt)
nDynAutoRef<T>派生自autoref<T> ,功能类似, 只是引用者"名字"是分配空间的
Nebula的内存接管机制:
内存链的构成: 1.block = nBlockHeader + nDataSize + nBlockPostfix 2.chunk = nChunkHeader + block + block + ...... 3.chunks列表记录每个chunk首址
一。当分配的内存大于1024时,只分配一整块(用NewChunk(size, 1)的第2参数为1 来分配) 这时新的chunk的分配是这样的: chunk = nChunkHeader + block + nBlockPostfix 而返回给用户的是block地址。
二。而小于1024的内存是这么分配的:
1. 先预先分配一堆大小不一样的小内存链: 在内存管理器(nMemManager)的构造时循环生成7个内存链(chunk), 每个chunk长16k, 例如: 第1个chunk分成16k/(1<<(i+N_MINBLOCK)个块(block), 其中的i = 0,每块的数据(data)大小为(1<<(i+N_MINBLOCK). 第1个chunk分成16k/(1<<(i+N_MINBLOCK)个块(block), 其中的i = 1,每块的数据(data)大小为(1<<(i+N_MINBLOCK). 如此循环,直到7个chunk分配完成。 因为Nebula定义的N_MINBLOCK=4,故 第1个链的数据大小是2的4次方即16个字节, 共16k/16=1024个块。 第2个链的数据大小是2的5次方即32个字节, 共16k/32=512个块。 第3个链的数据大小是2的6次方即64个字节, 共16k/64=256个块。 第4个链的数据大小是2的7次方即128个字节, 共16k/128=128个块。 第5个链的数据大小是2的8次方即256个字节, 共16k/256=64个块。 第6个链的数据大小是2的9次方即512个字节, 共16k/512=32个块。 第7个链的数据大小是2的10次方即1024个字节,共16k/1024=16个块。 每个链中的每一个数据块实际上会多出用户见不到的块头(nBlockHeader)和块尾 (nBlockPostfix) 块头中有个next指向下一个Block,另外块头和块尾各放一标志,用于当内存越界或内 存访问出乱时得知错误: nBlockHeader->magic == N_PREFIX_MAGIC_COOKIE nBlockPostfix->magic == N_POSTFIX_MAGIC_COOKIE
2. 当程序需要new出小块内存时从对应预分配的刚好或稍大的小内存链中取出来一块来 用, 并用它的nBlockHeader的next来保存它原来是属于第几个链(chunk)的,以便当这段 内存释放时放回到原来的链中.
Nebula树型目录及脚本接口
1. 首先nKernelServer调用ReadTocFiles从toc文件中把"输入类及对应dll名称"加入到 toc_list列表中
2. 然后脚本new nobj obj;时,tcl调用tclcmd_New告知nebula,
a. nebula的_openClass先从class_list查找,有则返回该类。
b. 没有则从toc_list中找,找到则装载dll并呼叫addmodules_func来设置此类对应 的创建函数、初始函数、 释放函数,同时调用它的初始化函数,这会触发调用n_initcmds(nClass* clazz)来注册该类对应的脚本函数 (包括函数名称及参数描述以及对应函数地址),然后再加到class_list列表中, 并返回该类。 最后用此类来产生obj对象后设置父子关系并调用此对象的initialize
|