/* ** 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) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // W3DSmudge.cpp //////////////////////////////////////////////////////////////////////////////// // Smudge System implementation // Author: Mark Wilczynski, June 2003 /////////////////////////////////////////////////////////////////////////////////////////////////// #include "Lib/Basetype.h" #include "always.h" #include "W3DDevice/GameClient/W3DSmudge.h" #include "W3DDevice/GameClient/W3DShaderManager.h" #include "Common/GameMemory.h" #include "GameClient/view.h" #include "GameClient/display.h" #include "WW3D2/texture.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/dx8wrapper.h" #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "WW3D2/sortingrenderer.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif SmudgeManager *TheSmudgeManager=NULL; W3DSmudgeManager::W3DSmudgeManager(void) { } W3DSmudgeManager::~W3DSmudgeManager() { ReleaseResources(); } void W3DSmudgeManager::init(void) { SmudgeManager::init(); ReAcquireResources(); } void W3DSmudgeManager::reset (void) { SmudgeManager::reset(); //base } void W3DSmudgeManager::ReleaseResources(void) { #ifdef USE_COPY_RECTS REF_PTR_RELEASE(m_backgroundTexture); #endif REF_PTR_RELEASE(m_indexBuffer); } //Make sure (SMUDGE_DRAW_SIZE * 12) < 65535 because that's the max index buffer size. #define SMUDGE_DRAW_SIZE 500 //draw at most 50 smudges per call. Tweak value to improve CPU/GPU parallelism. void W3DSmudgeManager::ReAcquireResources(void) { ReleaseResources(); SurfaceClass *surface=DX8Wrapper::_Get_DX8_Back_Buffer(); SurfaceClass::SurfaceDescription surface_desc; surface->Get_Description(surface_desc); REF_PTR_RELEASE(surface); #ifdef USE_COPY_RECTS m_backgroundTexture = MSGNEW("TextureClass") TextureClass(TheTacticalView->getWidth(),TheTacticalView->getHeight(),surface_desc.Format,MIP_LEVELS_1,TextureClass::POOL_DEFAULT, true); #endif m_backBufferWidth = surface_desc.Width; m_backBufferHeight = surface_desc.Height; m_indexBuffer=NEW_REF(DX8IndexBufferClass,(SMUDGE_DRAW_SIZE*4*3)); //allocate 4 triangles per smudge, each with 3 indices. // Fill up the IB with static vertex indices that will be used for all smudges. { DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); //quad of 4 triangles: // 0-----3 // |\ /| // | 4 | // |/ \| // 1-----2 Int vbCount=0; for (Int i=0; iGetRenderTarget(&surface); if (!surface) goto error; D3DSURFACE_DESC desc; surface->GetDesc(&desc); RECT srcRect; srcRect.left=oX; srcRect.top=oY; srcRect.right=oX+width; srcRect.bottom=oY+height; POINT dstPoint; dstPoint.x=0; dstPoint.y=0; hr=m_pDev->CreateImageSurface( width, height, desc.Format, &tempSurface); if (hr != S_OK) goto error; hr=m_pDev->CopyRects(surface,&srcRect,1,tempSurface,&dstPoint); if (hr != S_OK) goto error; D3DLOCKED_RECT lrect; hr=tempSurface->LockRect(&lrect,NULL,D3DLOCK_READONLY); if (hr != S_OK) goto error; tempSurface->GetDesc(&desc); if (desc.Size < bufSize) bufSize = desc.Size; memcpy(buf,lrect.pBits,bufSize); result = bufSize; error: if (surface) surface->Release(); if (tempSurface) tempSurface->Release(); return result; } #define UNIQUE_COLOR (0x12345678) #define BLOCK_SIZE (8) Bool W3DSmudgeManager::testHardwareSupport(void) { if (m_hardwareSupportStatus == SMUDGE_SUPPORT_UNKNOWN) { //we have not done the test yet. IDirect3DTexture8 *backTexture=W3DShaderManager::getRenderTexture(); if (!backTexture) { //do trivial test first to see if render target exists. m_hardwareSupportStatus = SMUDGE_SUPPORT_NO; return FALSE; } if (!W3DShaderManager::isRenderingToTexture()) return FALSE; //can't do the test unless we're rendering to texture. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); DX8Wrapper::Set_Material(vmat); REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset. ShaderClass shader=ShaderClass::_PresetOpaqueShader; shader.Set_Depth_Compare(ShaderClass::PASS_ALWAYS); shader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE); DX8Wrapper::Set_Shader(shader); DX8Wrapper::Set_Texture(0,NULL); DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices struct _TRANS_LIT_TEX_VERTEX { Vector4 p; DWORD color; // diffuse color float u; float v; } v[4]; //bottom right v[0].p = Vector4( BLOCK_SIZE-0.5f, BLOCK_SIZE-0.5f, 0.0f, 1.0f ); v[0].u = BLOCK_SIZE/(Real)TheDisplay->getWidth(); v[0].v = BLOCK_SIZE/(Real)TheDisplay->getHeight();; //top right v[1].p = Vector4( BLOCK_SIZE-0.5f, 0-0.5f, 0.0f, 1.0f ); v[1].u = BLOCK_SIZE/(Real)TheDisplay->getWidth(); v[1].v = 0; //bottom left v[2].p = Vector4( 0-0.5f, BLOCK_SIZE-0.5f, 0.0f, 1.0f ); v[2].u = 0; v[2].v = BLOCK_SIZE/(Real)TheDisplay->getHeight(); //top left v[3].p = Vector4( 0-0.5f, 0-0.5f, 0.0f, 1.0f ); v[3].u = 0; v[3].v = 0; v[0].color = UNIQUE_COLOR; v[1].color = UNIQUE_COLOR; v[2].color = UNIQUE_COLOR; v[3].color = UNIQUE_COLOR; LPDIRECT3DDEVICE8 pDev=DX8Wrapper::_Get_D3D_Device8(); //draw polygons like this is very inefficient but for only 2 triangles, it's //not worth bothering with index/vertex buffers. pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); DWORD refData[BLOCK_SIZE*BLOCK_SIZE]; memset(refData,0,sizeof(refData)); Int bufSize=copyRect((unsigned char *)refData,sizeof(refData),0,0,BLOCK_SIZE,BLOCK_SIZE); //copy area we just rendered using solid color if (!bufSize) { m_hardwareSupportStatus = SMUDGE_SUPPORT_NO; return FALSE; } DX8Wrapper::Set_DX8_Texture(0,backTexture); DWORD testData[BLOCK_SIZE*BLOCK_SIZE]; memset(testData,0xff,sizeof(testData)); v[0].color = 0xffffffff; v[1].color = 0xffffffff; v[2].color = 0xffffffff; v[3].color = 0xffffffff; pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); bufSize=copyRect((unsigned char *)testData,sizeof(testData),0,0,BLOCK_SIZE,BLOCK_SIZE); if (!bufSize) { m_hardwareSupportStatus = SMUDGE_SUPPORT_NO; return FALSE; } //compare the 2 buffers to see if they match. if (memcmp(testData,refData,bufSize) == 0) { m_hardwareSupportStatus = SMUDGE_SUPPORT_YES; return TRUE; } m_hardwareSupportStatus = SMUDGE_SUPPORT_NO; } return (SMUDGE_SUPPORT_YES == m_hardwareSupportStatus); } void W3DSmudgeManager::render(RenderInfoClass &rinfo) { //Verify that the card supports the effect. if (!testHardwareSupport()) return; CameraClass &camera=rinfo.Camera; Vector3 vsVert; Vector4 ssVert; Real uvSpanX,uvSpanY; Vector3 vertex_offsets[4] = { Vector3(-0.5f, 0.5f, 0.0f), Vector3(-0.5f, -0.5f, 0.0f), Vector3(0.5f, -0.5f, 0.0f), Vector3(0.5f, 0.5f, 0.0f) }; #define THE_COLOR (0x00ffeedd) UnsignedInt vertexDiffuse[5]={THE_COLOR,THE_COLOR,THE_COLOR,THE_COLOR,THE_COLOR}; Matrix4x4 proj; Matrix3D view; camera.Get_View_Matrix(&view); camera.Get_Projection_Matrix(&proj); SurfaceClass::SurfaceDescription surface_desc; #ifdef USE_COPY_RECTS SurfaceClass *background=m_backgroundTexture->Get_Surface_Level(); background->Get_Description(surface_desc); #else D3DSURFACE_DESC D3DDesc; IDirect3DTexture8 *backTexture=W3DShaderManager::getRenderTexture(); if (!backTexture || !W3DShaderManager::isRenderingToTexture()) return; //this card doesn't support render targets. backTexture->GetLevelDesc(0,&D3DDesc); surface_desc.Width = D3DDesc.Width; surface_desc.Height = D3DDesc.Height; #endif Real texClampX = (Real)TheTacticalView->getWidth()/(Real)surface_desc.Width; Real texClampY = (Real)TheTacticalView->getHeight()/(Real)surface_desc.Height; Real texScaleX = texClampX*0.5f; Real texScaleY = texClampY*0.5f; //Do a first pass over the smudges to determine how many are visible //and to fill in their world-space positions and screen uv coordinates. //TODO: Optimize out this extra pass! //TODO: Find size of screen rectangle that actually needs copying. SmudgeSet *set=m_usedSmudgeSetList.Head(); //first set that didn't fit into render batch. Int count = 0; if (set) { //there are possibly some smudges to render, so make sure background particles have finished drawing. SortingRendererClass::Flush(); //draw sorted translucent polys like particles. } while (set) { Smudge *smudge=set->getUsedSmudgeList().Head(); while (smudge) { //Get view-space center Matrix3D::Transform_Vector(view,smudge->m_pos,&vsVert); //Get 5 view-space vertices Smudge::smudgeVertex *verts=smudge->m_verts; //Do center vertex outside 'for' loop since it's different. verts[4].pos = vsVert; for (Int i=0; i<4; i++) { verts[i].pos = vsVert + vertex_offsets[i] * smudge->m_size; //Ge uv coordinates for each vertex ssVert = proj * verts[i].pos; Real oow = 1.0f/ssVert.W; ssVert *= oow; //returned in camera space which is -1,-1 (bottom-left) to 1,1 (top-right) //convert camera space to uv space: 0,0 (top-left), 1,1 (bottom-right) verts[i].uv.Set((ssVert.X+1.0f)*texScaleX,(1.0f-ssVert.Y)*texScaleY); Vector2 &thisUV=verts[i].uv; //Clamp coordinates so we're not referencing texels outside the view. if (thisUV.X > texClampX) smudge->m_offset.X = 0; else if (thisUV.X < 0) smudge->m_offset.X = 0; if (thisUV.Y > texClampY) smudge->m_offset.Y = 0; else if (thisUV.Y < 0) smudge->m_offset.Y = 0; } //Finish center vertex //Ge uv coordinates by interpolating corner uv coordinates and applying desired offset. uvSpanX=verts[3].uv.X - verts[0].uv.X; uvSpanY=verts[1].uv.Y - verts[0].uv.Y; verts[4].uv.X=verts[0].uv.X+uvSpanX*(0.5f+smudge->m_offset.X); verts[4].uv.Y=verts[0].uv.Y+uvSpanY*(0.5f+smudge->m_offset.X); count++; //increment visible smudge count. smudge=smudge->Succ(); } set=set->Succ(); //advance to next node. } if (!count) { #ifdef USE_COPY_RECTS REF_PTR_RELEASE(background); #endif return; //nothing to render. } #ifdef USE_COPY_RECTS SurfaceClass *backBuffer=DX8Wrapper::_Get_DX8_Back_Buffer(); backBuffer->Get_Description(surface_desc); //Copy the area of backbuffer occupied by smudges into an alternate buffer. background->Copy(0,0,0,0,surface_desc.Width,surface_desc.Height,backBuffer); REF_PTR_RELEASE(background); REF_PTR_RELEASE(backBuffer); #endif Matrix4x4 identity(true); DX8Wrapper::Set_Transform(D3DTS_WORLD,identity); DX8Wrapper::Set_Transform(D3DTS_VIEW,identity); DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0); //DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueSpriteShader); DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); #ifdef USE_COPY_RECTS DX8Wrapper::Set_Texture(0,m_backgroundTexture); #else DX8Wrapper::Set_DX8_Texture(0,backTexture); //Need these states in case texture is non-power-of-2 DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSW, D3DTADDRESS_CLAMP); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MIPFILTER, D3DTEXF_NONE); #endif VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); DX8Wrapper::Set_Material(vmat); REF_PTR_RELEASE(vmat); DX8Wrapper::Apply_Render_State_Changes(); //Disable reading texture alpha since it's undefined. //DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1); DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG2); Int smudgesRemaining=count; set=m_usedSmudgeSetList.Head(); //first smudge set that needs rendering. Smudge *remainingSmudgeStart=set->getUsedSmudgeList().Head(); //first smudge that needs rendering. while (smudgesRemaining) //keep drawing smudges until we run out. { //Now that we know how many smudges need rendering, allocate vertex buffer space and copy verts. count=smudgesRemaining; if (count > SMUDGE_DRAW_SIZE) count = SMUDGE_DRAW_SIZE; Int smudgesInRenderBatch=0; DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,count*5); //allocate 5 verts per smudge. { DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array(); while (set) { Smudge *smudge=remainingSmudgeStart; while (smudge) { Smudge::smudgeVertex *smVerts = smudge->m_verts; //Check if we exceeded maximum number of smudges allowed per draw call. if (smudgesInRenderBatch >= count) { remainingSmudgeStart = smudge; goto flushSmudges; } //Set center vertex opacity. vertexDiffuse[4] = ((Int)(smudge->m_opacity * 255.0f) << 24) | THE_COLOR; for (Int i=0; i<5; i++) { verts->x=smVerts->pos.X; verts->y=smVerts->pos.Y; verts->z=smVerts->pos.Z; verts->nx=0; //keep AGP write-combining active verts->ny=0; verts->nz=0; verts->diffuse=vertexDiffuse[i]; //set to transparent verts->u1=smVerts->uv.X; verts->v1=smVerts->uv.Y; verts->u2=0; //keep AGP write-combining active verts->v2=0; verts++; smVerts++; } smudgesInRenderBatch++; smudge=smudge->Succ(); } set=set->Succ(); //advance to next node. if (set) //start next batch at beginning of set. remainingSmudgeStart = set->getUsedSmudgeList().Head(); } flushSmudges: DX8Wrapper::Set_Vertex_Buffer(vb_access); } DX8Wrapper::Draw_Triangles( 0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5); //Debug Code which draws outline around smudge /* DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE); DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG2); DX8Wrapper::Draw_Triangles( 0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE); DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1); */ smudgesRemaining -= smudgesInRenderBatch; } DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_MODULATE); DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE); }