/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : WW3D * * * * $Archive:: /Commando/Code/ww3d2/shdrenderer.cpp $* * * Org Author:: Jani_p * * * $Author:: Kenny_m * * $Modtime:: 7/29/02 1:50p $* * * * $Revision:: 3 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * 6/02/02 5:59p KM Added render info and light environment support $* * 7/29/02 1:50p KM Added VB and IB usage flags for software vertex shaders * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "shdrenderer.h" #include "shdforcelinks.h" #include "shdmesh.h" #include "shdsubmesh.h" #include "shddef.h" #include "shddefmanager.h" #include "shdclassids.h" #include "wwdebug.h" #include "dx8vertexbuffer.h" #include "dx8indexbuffer.h" #include "dx8wrapper.h" #include "rinfo.h" #include "camera.h" #include "texture.h" #include "ww3dformat.h" #include "wwprofile.h" #include "sortingrenderer.h" #include "meshmatdesc.h" static DynamicVectorClass _TempVertexBuffer; static DynamicVectorClass _TempNormalBuffer; ShdRendererClass* ShdRendererClass::ShdRenderer=NULL; ShdRendererClass::ShdRendererClass() { } ShdRendererClass::~ShdRendererClass() { } ///////////////////////////////////////////////////////////////////////////////////// // Initialize the rendering system. This is the place to create different versions // (such as DX8, DX9, OGL) of the rendering system, if needed. ///////////////////////////////////////////////////////////////////////////////////// void ShdRendererClass::Init() { SHD_Force_Links(); WWASSERT(!ShdRenderer); ShdRenderer=new ShdDX8RendererClass(); Init_Shaders(); } void ShdRendererClass::Init_Shaders() { ShdDefClass* def; for (int i=SHDDEF_CLASSID_DUMMY+1; iInit(); REF_PTR_RELEASE(def); } } } ///////////////////////////////////////////////////////////////////////////////////// // Release the renderer system. At this point, all the meshes must have been // unregistered. ///////////////////////////////////////////////////////////////////////////////////// void ShdRendererClass::Shutdown() { Shutdown_Shaders(); WWASSERT(ShdRenderer); delete ShdRenderer; ShdRenderer=0; } void ShdRendererClass::Shutdown_Shaders() { ShdDefClass* def; for (int i=SHDDEF_CLASSID_DUMMY+1; iShutdown(); REF_PTR_RELEASE(def); } } } ///////////////////////////////////////////////////////////////////////////////////// // DirectX8 renderer utility class declaration ///////////////////////////////////////////////////////////////////////////////////// RendererListContainerClass::RendererListContainerClass(int pass) : Pass(pass) { } RendererListContainerClass::~RendererListContainerClass() { Unregister_All(); } void RendererListContainerClass::Register_Renderer(ShdRendererNodeClass* node) { WWASSERT(node); node->Add_Ref(); node->Set_Renderer_List_Container(this,Pass); LinkedNodes.Add_Tail(node); } void RendererListContainerClass::Unregister_All() { while (!LinkedNodes.Is_Empty()) { ShdRendererNodeClass* node=LinkedNodes.Remove_Head(); // REF_PTR_RELEASE(node); delete node; } } void RendererListContainerClass::Flush() { ShdRendererNodeClass* prev_node=NULL; while (!VisibleNodes.Is_Empty()) { ShdRendererNodeClass* node=VisibleNodes.Remove_Head(); node->Apply_Shared_Shader_Settings(prev_node,Pass); node->Flush(Pass); prev_node=node; } } class ShdDX8RendererClass::MeshContainerClass : public RefCountClass { public: MeshContainerClass(); virtual ~MeshContainerClass(); ShdRendererNodeClass* Register_Mesh(ShdMeshClass* mesh,ShdSubMeshClass* sub_mesh); // void Add_Visible_Node(ShdRendererNodeClass* node,int pass); void Flush(); private: // ShdRendererNodeList VisibleNodeList[SHD_MAX_PASSES]; RendererListContainerList RendererListContainers[SHD_MAX_PASSES]; }; /** ** ShdDX8RendererNode */ class ShdDX8RendererNodeClass : public ShdRendererNodeClass { public: ShdDX8RendererNodeClass(ShdDX8RendererClass::MeshContainerClass* container, ShdMeshClass* mesh,ShdSubMeshClass* sub_mesh); virtual ~ShdDX8RendererNodeClass(); virtual void Render(RenderInfoClass& rinfo); virtual void Flush(int pass); virtual void Apply_Shared_Shader_Settings(ShdRendererNodeClass* prev,int pass); virtual bool Greater_Than(const ShdRendererNodeClass&, int pass) const; virtual bool Similar_Enough(const ShdRendererNodeClass&, int pass) const; private: ShdDX8RendererClass::MeshContainerClass* Container; ShdMeshClass* Mesh; ShdSubMeshClass* SubMesh; VertexBufferClass** VertexBuffers; IndexBufferClass* IndexBuffer; LightEnvironmentClass LightEnvironment; // todo KJM optimize for output lights RenderInfoClass* RenderInfo; }; ///////////////////////////////////////////////////////////////////////////////////// // DirectX8 renderer implementation ///////////////////////////////////////////////////////////////////////////////////// ShdDX8RendererClass::ShdDX8RendererClass() { MeshCategories=new MeshContainerClass*[SHDDEF_CLASSID_LAST]; for (int i=0;i(&s); WWASSERT(SubMesh && src->SubMesh); return SubMesh->Peek_Shader()->Greater_Than(*src->SubMesh->Peek_Shader(),pass); } bool ShdDX8RendererNodeClass::Similar_Enough(const ShdRendererNodeClass& s, int pass) const { // Danger! Assuming ShdDX8RendererNodeClass! const ShdDX8RendererNodeClass* src=static_cast(&s); WWASSERT(SubMesh && src->SubMesh); return SubMesh->Peek_Shader()->Similar_Enough(*src->SubMesh->Peek_Shader(),pass); } ///////////////////////////////////////////////////////////////////////////////////// // Register mesh to the rendering system ///////////////////////////////////////////////////////////////////////////////////// ShdRendererNodeClass* ShdDX8RendererClass::Register_Mesh ( ShdMeshClass* mesh, ShdSubMeshClass* sub_mesh ) { ShdInterfaceClass* shd=sub_mesh->Peek_Shader(); const ShdDefClass* def=shd->Peek_Definition(); uint32 class_id=def->Get_Class_ID(); WWASSERT(class_idRegister_Mesh(mesh,sub_mesh); } ///////////////////////////////////////////////////////////////////////////////////// // Flush the list of visible meshes ///////////////////////////////////////////////////////////////////////////////////// void ShdDX8RendererClass::Flush() { WWPROFILE("ShdDX8RendererClass::Flush"); DX8Wrapper::Apply_Render_State_Changes(); DX8Wrapper::Invalidate_Cached_Render_States(); DX8Wrapper::Apply_Default_State(); SNAPSHOT_SAY(("ShdDX8RendererClass::Flush()\n")); for (int i=0;iFlush(); } } DX8Wrapper::Invalidate_Cached_Render_States(); DX8Wrapper::Apply_Default_State(); } ///////////////////////////////////////////////////////////////////////////////////// // MeshContiner is used for each shader type. Meshes are assigned to a container // when they're first rendered and they link themselves to container's visible list // each frame they're visible. ///////////////////////////////////////////////////////////////////////////////////// ShdDX8RendererClass::MeshContainerClass::MeshContainerClass() { } ShdDX8RendererClass::MeshContainerClass::~MeshContainerClass() { for (int pass=0; passUnregister_All(); REF_PTR_RELEASE(cont); } } } ///////////////////////////////////////////////////////////////////////////////////// // RendererNodeClass acts as a link between ShdMeshClass, ShdSubMeshClass and the // rendering system. ///////////////////////////////////////////////////////////////////////////////////// ShdRendererNodeClass* ShdDX8RendererClass::MeshContainerClass::Register_Mesh ( ShdMeshClass* mesh, ShdSubMeshClass* sub_mesh ) { ShdDX8RendererNodeClass* node=new ShdDX8RendererNodeClass(this,mesh,sub_mesh); ShdInterfaceClass* shdi=sub_mesh->Peek_Shader(); WWASSERT(shdi); for (int pass=0; passGet_Pass_Count(); pass++) { RendererListContainerIterator ite(&RendererListContainers[pass]); ite.Last(); while (!ite.Is_Done()) { RendererListContainerClass* cont=ite.Peek_Obj(); if (cont) { ShdRendererNodeListIterator ite2(&cont->Peek_Linked_Nodes()); ite2.First(); // Container should delete itself when the last node is removed, so we must // never have an empty node. WWASSERT(!ite2.Is_Done()); ShdRendererNodeClass* obj=ite2.Peek_Obj(); if (obj) { if (node->Greater_Than(*obj,pass)) { // If similar enough, add to the same renderer container, otherwise create a new one if (node->Similar_Enough(*obj,pass)) { cont->Register_Renderer(node); } else { RendererListContainerClass* new_cont=new RendererListContainerClass(pass); new_cont->Register_Renderer(node); RendererListContainers[pass].Add_After(new_cont,new_cont); } break; } } } ite.Prev(); } if (ite.Is_Done()) { RendererListContainerClass* new_cont=new RendererListContainerClass(pass); new_cont->Register_Renderer(node); RendererListContainers[pass].Add(new_cont); } } return node; } ///////////////////////////////////////////////////////////////////////////////////// // Render all visible nodes and clear the visible list. ///////////////////////////////////////////////////////////////////////////////////// void ShdDX8RendererClass::MeshContainerClass::Flush() { SNAPSHOT_SAY(("ShdDX8RendererClass::MeshContainerClass::Flush()\n")); for (int pass=0; passFlush(); ite.Next(); } } } /*********************************************************************************************** * ShdDX8RendererNodeClass::ShdDX8RendererNodeClass -- * Init the mesh for rendering... this node is used for all subsequent rendering of * the particular mesh. * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/20/2002 jp : Created * 5/26/2002 kjm : Updated stream handling for per-pixel shaders * * 6/02/2002 kjm : Added light environment handling * 7/29/2002 kjm : Added VB and IB usage flags for software vertex shaders *=============================================================================================*/ ShdDX8RendererNodeClass::ShdDX8RendererNodeClass ( ShdDX8RendererClass::MeshContainerClass* container, ShdMeshClass* mesh, ShdSubMeshClass* sub_mesh ) : Container(container), Mesh(NULL), SubMesh(NULL), VertexBuffers(NULL), IndexBuffer(NULL) { REF_PTR_SET(Container,container); REF_PTR_SET(Mesh,mesh); REF_PTR_SET(SubMesh,sub_mesh); // create usage depending on associated shader's processing behaviour KM DX8IndexBufferClass::UsageType ib_usage=DX8IndexBufferClass::USAGE_DEFAULT; if (!SubMesh->Peek_Shader()->Use_HW_Vertex_Processing()) { ib_usage=DX8IndexBufferClass::USAGE_SOFTWAREPROCESSING; } int count=SubMesh->Get_Visible_Polygon_Count(); if (!count) count=SubMesh->Get_Polygon_Count(); unsigned index_count=count*3; if (SubMesh->Is_Sorting()) { IndexBuffer=new SortingIndexBufferClass(index_count); } else { IndexBuffer=new DX8IndexBufferClass(index_count,ib_usage); } IndexBufferClass::WriteLockClass ilock(IndexBuffer); const TriIndex* indices=SubMesh->Get_Polygon_Array(); int i; int j=0; for (i=SubMesh->Get_First_Visible_Polygon();iGet_First_Visible_Polygon();++i) { ilock.Get_Index_Array()[j++]=indices[i][0]; ilock.Get_Index_Array()[j++]=indices[i][1]; ilock.Get_Index_Array()[j++]=indices[i][2]; } // Don't use static vertex buffers if skin if (SubMesh->Get_Flag(MeshGeometryClass::SKIN)) { return; } // Compose the vertex and index buffers VertexStreamStruct vss; vss.Locations=SubMesh->Get_Vertex_Array(); vss.Normals=SubMesh->Get_Vertex_Normal_Array(); for (unsigned stage=0;stageGet_UV_Array(stage); } vss.DiffuseInt=SubMesh->Get_Diffuse_Array(); vss.S=SubMesh->Get_Tangent_Basis_S_Array(); vss.T=SubMesh->Get_Tangent_Basis_T_Array(); vss.SxT=SubMesh->Get_Tangent_Basis_SxT_Array(); unsigned vertex_count=SubMesh->Get_Vertex_Count(); unsigned stream_count=SubMesh->Peek_Shader()->Get_Vertex_Stream_Count(); WWASSERT(stream_count>0); // create usage depending on associated shader's processing behaviour KM DX8VertexBufferClass::UsageType vb_usage=DX8VertexBufferClass::USAGE_DEFAULT; if (!SubMesh->Peek_Shader()->Use_HW_Vertex_Processing()) { vb_usage=DX8VertexBufferClass::USAGE_SOFTWAREPROCESSING; } VertexBuffers=new VertexBufferClass*[stream_count]; unsigned n; for (n=0;nPeek_Shader()->Get_Vertex_Size(n); if (SubMesh->Is_Sorting()) { VertexBuffers[n]= new SortingVertexBufferClass(vertex_count); } else { VertexBuffers[n]= new DX8VertexBufferClass ( 0, vertex_count, vb_usage, vertex_size ); } VertexBufferClass::WriteLockClass vlock(VertexBuffers[n]); SubMesh->Peek_Shader()->Copy_Vertex_Stream ( n, vlock.Get_Vertex_Array(), vss, SubMesh->Get_Vertex_Count() ); } } ShdDX8RendererNodeClass::~ShdDX8RendererNodeClass() { unsigned stream_count=SubMesh->Peek_Shader()->Get_Vertex_Stream_Count(); unsigned n; for (n=0;nPeek_Shader()->Pass_Selection(Mesh,RenderInfo,cur_pass)) { return; } SNAPSHOT_SAY(("ShdDX8RendererNodeClass::Flush(%d) - %s\n",cur_pass,SubMesh->Get_Name())); // BEGIN OF SKIN CODE if (SubMesh->Get_Flag(MeshGeometryClass::SKIN)) { DX8Wrapper::Set_Vertex_Buffer(NULL); // Free up the reference to the current vertex buffer // (in case it is the dynamic, which may have to be resized) unsigned vertex_count=SubMesh->Get_Vertex_Count(); unsigned stream_count=SubMesh->Peek_Shader()->Get_Vertex_Stream_Count(); WWASSERT(stream_count>0); // For now there are restrictions in vertex buffer format with skinning FVFInfoClass fi(dynamic_fvf_type); WWASSERT(SubMesh->Peek_Shader()->Get_Vertex_Size(0)==fi.Get_FVF_Size()); DynamicVBAccessClass vb( SubMesh->Is_Sorting() ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC_DX8, dynamic_fvf_type, vertex_count); SNAPSHOT_SAY(("DynamicVBAccess - %s - %d vertices\n",SubMesh->Is_Sorting() ? "sorting" : "non-sorting",vertex_count)); if (_TempVertexBuffer.Length() < (int)vertex_count) _TempVertexBuffer.Resize(vertex_count); if (_TempNormalBuffer.Length() < (int)vertex_count) _TempNormalBuffer.Resize(vertex_count); Vector3* loc=&(_TempVertexBuffer[0]); Vector3* norm=&(_TempNormalBuffer[0]); WWASSERT(Mesh->Get_Container()); SubMesh->Get_Deformed_Vertices(loc,norm,Mesh->Get_Container()->Get_HTree()); // Compose the vertex and index buffers VertexStreamStruct vss; vss.Locations=loc; vss.Normals=norm; for (unsigned stage=0;stageGet_UV_Array(stage); } vss.DiffuseInt=SubMesh->Get_Diffuse_Array(); vss.S=SubMesh->Get_Tangent_Basis_S_Array(); vss.T=SubMesh->Get_Tangent_Basis_T_Array(); vss.SxT=SubMesh->Get_Tangent_Basis_SxT_Array(); WWASSERT(stream_count==1); { DynamicVBAccessClass::WriteLockClass vlock(&vb); SubMesh->Peek_Shader()->Copy_Vertex_Stream ( 0,//stream vlock.Get_Formatted_Vertex_Array(), vss, SubMesh->Get_Vertex_Count() ); } DX8Wrapper::Set_Vertex_Buffer(vb);//stream 0 DX8Wrapper::Set_Index_Buffer(IndexBuffer,0); DX8Wrapper::Set_Transform(D3DTS_WORLD,Matrix3D(true));//Mesh->Get_Transform()); DX8Wrapper::Set_Light_Environment(&LightEnvironment); SubMesh->Peek_Shader()->Apply_Instance(cur_pass, *RenderInfo); if (SubMesh->Is_Sorting()) { SortingRendererClass::Insert_Triangles ( 0, SubMesh->Get_Visible_Polygon_Count() ? SubMesh->Get_Visible_Polygon_Count() : SubMesh->Get_Polygon_Count(), 0, SubMesh->Get_Vertex_Count() ); } else { DX8Wrapper::Draw_Triangles ( 0, SubMesh->Get_Visible_Polygon_Count() ? SubMesh->Get_Visible_Polygon_Count() : SubMesh->Get_Polygon_Count(), 0, SubMesh->Get_Vertex_Count() ); } return; } // END OF SKIN CODE for (unsigned n=0;nPeek_Shader()->Get_Vertex_Stream_Count();++n) { DX8Wrapper::Set_Vertex_Buffer(VertexBuffers[n],n); } DX8Wrapper::Set_Index_Buffer(IndexBuffer,0); DX8Wrapper::Set_Transform(D3DTS_WORLD,Mesh->Get_Transform()); DX8Wrapper::Set_Light_Environment(&LightEnvironment); SubMesh->Peek_Shader()->Apply_Instance(cur_pass, *RenderInfo); if (SubMesh->Is_Sorting()) { SortingRendererClass::Insert_Triangles ( 0, SubMesh->Get_Visible_Polygon_Count() ? SubMesh->Get_Visible_Polygon_Count() : SubMesh->Get_Polygon_Count(), 0, SubMesh->Get_Vertex_Count() ); } else { DX8Wrapper::Draw_Triangles ( 0, SubMesh->Get_Visible_Polygon_Count() ? SubMesh->Get_Visible_Polygon_Count() : SubMesh->Get_Polygon_Count(), 0, SubMesh->Get_Vertex_Count() ); } } void ShdDX8RendererNodeClass::Apply_Shared_Shader_Settings(ShdRendererNodeClass* prev_node, int pass) { // If the previously set shader is not the same type as this, we need to set // all render states to default state. if (prev_node) { ShdDX8RendererNodeClass* prev_dx8_node=static_cast(prev_node); // Illegal to pass another type! ShdInterfaceClass* shd=prev_dx8_node->SubMesh->Peek_Shader(); if (shd->Get_Class_ID()!=SubMesh->Peek_Shader()->Get_Class_ID()) { DX8Wrapper::Apply_Default_State(); } } SubMesh->Peek_Shader()->Apply_Shared(pass, *RenderInfo); }