W3DSmudge.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // W3DSmudge.cpp ////////////////////////////////////////////////////////////////////////////////
  24. // Smudge System implementation
  25. // Author: Mark Wilczynski, June 2003
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. #include "Lib/Basetype.h"
  28. #include "always.h"
  29. #include "W3DDevice/GameClient/W3DSmudge.h"
  30. #include "W3DDevice/GameClient/W3DShaderManager.h"
  31. #include "Common/GameMemory.h"
  32. #include "GameClient/view.h"
  33. #include "GameClient/display.h"
  34. #include "WW3D2/texture.h"
  35. #include "WW3D2/dx8indexbuffer.h"
  36. #include "WW3D2/dx8wrapper.h"
  37. #include "WW3D2/rinfo.h"
  38. #include "WW3D2/camera.h"
  39. #include "WW3D2/sortingrenderer.h"
  40. #ifdef _INTERNAL
  41. // for occasional debugging...
  42. //#pragma optimize("", off)
  43. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  44. #endif
  45. SmudgeManager *TheSmudgeManager=NULL;
  46. W3DSmudgeManager::W3DSmudgeManager(void)
  47. {
  48. }
  49. W3DSmudgeManager::~W3DSmudgeManager()
  50. {
  51. ReleaseResources();
  52. }
  53. void W3DSmudgeManager::init(void)
  54. {
  55. SmudgeManager::init();
  56. ReAcquireResources();
  57. }
  58. void W3DSmudgeManager::reset (void)
  59. {
  60. SmudgeManager::reset(); //base
  61. }
  62. void W3DSmudgeManager::ReleaseResources(void)
  63. {
  64. #ifdef USE_COPY_RECTS
  65. REF_PTR_RELEASE(m_backgroundTexture);
  66. #endif
  67. REF_PTR_RELEASE(m_indexBuffer);
  68. }
  69. //Make sure (SMUDGE_DRAW_SIZE * 12) < 65535 because that's the max index buffer size.
  70. #define SMUDGE_DRAW_SIZE 500 //draw at most 50 smudges per call. Tweak value to improve CPU/GPU parallelism.
  71. void W3DSmudgeManager::ReAcquireResources(void)
  72. {
  73. ReleaseResources();
  74. SurfaceClass *surface=DX8Wrapper::_Get_DX8_Back_Buffer();
  75. SurfaceClass::SurfaceDescription surface_desc;
  76. surface->Get_Description(surface_desc);
  77. REF_PTR_RELEASE(surface);
  78. #ifdef USE_COPY_RECTS
  79. m_backgroundTexture = MSGNEW("TextureClass") TextureClass(TheTacticalView->getWidth(),TheTacticalView->getHeight(),surface_desc.Format,MIP_LEVELS_1,TextureClass::POOL_DEFAULT, true);
  80. #endif
  81. m_backBufferWidth = surface_desc.Width;
  82. m_backBufferHeight = surface_desc.Height;
  83. m_indexBuffer=NEW_REF(DX8IndexBufferClass,(SMUDGE_DRAW_SIZE*4*3)); //allocate 4 triangles per smudge, each with 3 indices.
  84. // Fill up the IB with static vertex indices that will be used for all smudges.
  85. {
  86. DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer);
  87. UnsignedShort *ib=lockIdxBuffer.Get_Index_Array();
  88. //quad of 4 triangles:
  89. // 0-----3
  90. // |\ /|
  91. // | 4 |
  92. // |/ \|
  93. // 1-----2
  94. Int vbCount=0;
  95. for (Int i=0; i<SMUDGE_DRAW_SIZE; i++)
  96. {
  97. //Top
  98. ib[0]=vbCount;
  99. ib[1]=vbCount+4;
  100. ib[2]=vbCount+3;
  101. //Right
  102. ib[3]=vbCount+3;
  103. ib[4]=vbCount+4;
  104. ib[5]=vbCount+2;
  105. //Bottom
  106. ib[6]=vbCount+2;
  107. ib[7]=vbCount+4;
  108. ib[8]=vbCount+1;
  109. //Left
  110. ib[9]=vbCount+1;
  111. ib[10]=vbCount+4;
  112. ib[11]=vbCount+0;
  113. vbCount += 5;
  114. ib+=12;
  115. }
  116. }
  117. }
  118. /*Copies a portion of the current render target into a specified buffer*/
  119. Int copyRect(unsigned char *buf, Int bufSize, int oX, int oY, int width, int height)
  120. {
  121. IDirect3DSurface8 *surface=NULL; ///<previous render target
  122. IDirect3DSurface8 *tempSurface=NULL;
  123. Int result = 0;
  124. HRESULT hr = S_OK;
  125. LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8();
  126. if (!m_pDev)
  127. goto error;
  128. m_pDev->GetRenderTarget(&surface);
  129. if (!surface)
  130. goto error;
  131. D3DSURFACE_DESC desc;
  132. surface->GetDesc(&desc);
  133. RECT srcRect;
  134. srcRect.left=oX;
  135. srcRect.top=oY;
  136. srcRect.right=oX+width;
  137. srcRect.bottom=oY+height;
  138. POINT dstPoint;
  139. dstPoint.x=0;
  140. dstPoint.y=0;
  141. hr=m_pDev->CreateImageSurface( width, height, desc.Format, &tempSurface);
  142. if (hr != S_OK)
  143. goto error;
  144. hr=m_pDev->CopyRects(surface,&srcRect,1,tempSurface,&dstPoint);
  145. if (hr != S_OK)
  146. goto error;
  147. D3DLOCKED_RECT lrect;
  148. hr=tempSurface->LockRect(&lrect,NULL,D3DLOCK_READONLY);
  149. if (hr != S_OK)
  150. goto error;
  151. tempSurface->GetDesc(&desc);
  152. if (desc.Size < bufSize)
  153. bufSize = desc.Size;
  154. memcpy(buf,lrect.pBits,bufSize);
  155. result = bufSize;
  156. error:
  157. if (surface)
  158. surface->Release();
  159. if (tempSurface)
  160. tempSurface->Release();
  161. return result;
  162. }
  163. #define UNIQUE_COLOR (0x12345678)
  164. #define BLOCK_SIZE (8)
  165. Bool W3DSmudgeManager::testHardwareSupport(void)
  166. {
  167. if (m_hardwareSupportStatus == SMUDGE_SUPPORT_UNKNOWN)
  168. { //we have not done the test yet.
  169. IDirect3DTexture8 *backTexture=W3DShaderManager::getRenderTexture();
  170. if (!backTexture)
  171. { //do trivial test first to see if render target exists.
  172. m_hardwareSupportStatus = SMUDGE_SUPPORT_NO;
  173. return FALSE;
  174. }
  175. if (!W3DShaderManager::isRenderingToTexture())
  176. return FALSE; //can't do the test unless we're rendering to texture.
  177. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE);
  178. DX8Wrapper::Set_Material(vmat);
  179. REF_PTR_RELEASE(vmat); //no need to keep a reference since it's a preset.
  180. ShaderClass shader=ShaderClass::_PresetOpaqueShader;
  181. shader.Set_Depth_Compare(ShaderClass::PASS_ALWAYS);
  182. shader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE);
  183. DX8Wrapper::Set_Shader(shader);
  184. DX8Wrapper::Set_Texture(0,NULL);
  185. DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices
  186. struct _TRANS_LIT_TEX_VERTEX {
  187. Vector4 p;
  188. DWORD color; // diffuse color
  189. float u;
  190. float v;
  191. } v[4];
  192. //bottom right
  193. v[0].p = Vector4( BLOCK_SIZE-0.5f, BLOCK_SIZE-0.5f, 0.0f, 1.0f );
  194. v[0].u = BLOCK_SIZE/(Real)TheDisplay->getWidth(); v[0].v = BLOCK_SIZE/(Real)TheDisplay->getHeight();;
  195. //top right
  196. v[1].p = Vector4( BLOCK_SIZE-0.5f, 0-0.5f, 0.0f, 1.0f );
  197. v[1].u = BLOCK_SIZE/(Real)TheDisplay->getWidth(); v[1].v = 0;
  198. //bottom left
  199. v[2].p = Vector4( 0-0.5f, BLOCK_SIZE-0.5f, 0.0f, 1.0f );
  200. v[2].u = 0; v[2].v = BLOCK_SIZE/(Real)TheDisplay->getHeight();
  201. //top left
  202. v[3].p = Vector4( 0-0.5f, 0-0.5f, 0.0f, 1.0f );
  203. v[3].u = 0; v[3].v = 0;
  204. v[0].color = UNIQUE_COLOR;
  205. v[1].color = UNIQUE_COLOR;
  206. v[2].color = UNIQUE_COLOR;
  207. v[3].color = UNIQUE_COLOR;
  208. LPDIRECT3DDEVICE8 pDev=DX8Wrapper::_Get_D3D_Device8();
  209. //draw polygons like this is very inefficient but for only 2 triangles, it's
  210. //not worth bothering with index/vertex buffers.
  211. pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1);
  212. pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX));
  213. DWORD refData[BLOCK_SIZE*BLOCK_SIZE];
  214. memset(refData,0,sizeof(refData));
  215. Int bufSize=copyRect((unsigned char *)refData,sizeof(refData),0,0,BLOCK_SIZE,BLOCK_SIZE); //copy area we just rendered using solid color
  216. if (!bufSize)
  217. {
  218. m_hardwareSupportStatus = SMUDGE_SUPPORT_NO;
  219. return FALSE;
  220. }
  221. DX8Wrapper::Set_DX8_Texture(0,backTexture);
  222. DWORD testData[BLOCK_SIZE*BLOCK_SIZE];
  223. memset(testData,0xff,sizeof(testData));
  224. v[0].color = 0xffffffff;
  225. v[1].color = 0xffffffff;
  226. v[2].color = 0xffffffff;
  227. v[3].color = 0xffffffff;
  228. pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX));
  229. bufSize=copyRect((unsigned char *)testData,sizeof(testData),0,0,BLOCK_SIZE,BLOCK_SIZE);
  230. if (!bufSize)
  231. {
  232. m_hardwareSupportStatus = SMUDGE_SUPPORT_NO;
  233. return FALSE;
  234. }
  235. //compare the 2 buffers to see if they match.
  236. if (memcmp(testData,refData,bufSize) == 0)
  237. {
  238. m_hardwareSupportStatus = SMUDGE_SUPPORT_YES;
  239. return TRUE;
  240. }
  241. m_hardwareSupportStatus = SMUDGE_SUPPORT_NO;
  242. }
  243. return (SMUDGE_SUPPORT_YES == m_hardwareSupportStatus);
  244. }
  245. void W3DSmudgeManager::render(RenderInfoClass &rinfo)
  246. {
  247. //Verify that the card supports the effect.
  248. if (!testHardwareSupport())
  249. return;
  250. CameraClass &camera=rinfo.Camera;
  251. Vector3 vsVert;
  252. Vector4 ssVert;
  253. Real uvSpanX,uvSpanY;
  254. Vector3 vertex_offsets[4] = {
  255. Vector3(-0.5f, 0.5f, 0.0f),
  256. Vector3(-0.5f, -0.5f, 0.0f),
  257. Vector3(0.5f, -0.5f, 0.0f),
  258. Vector3(0.5f, 0.5f, 0.0f)
  259. };
  260. #define THE_COLOR (0x00ffeedd)
  261. UnsignedInt vertexDiffuse[5]={THE_COLOR,THE_COLOR,THE_COLOR,THE_COLOR,THE_COLOR};
  262. Matrix4x4 proj;
  263. Matrix3D view;
  264. camera.Get_View_Matrix(&view);
  265. camera.Get_Projection_Matrix(&proj);
  266. SurfaceClass::SurfaceDescription surface_desc;
  267. #ifdef USE_COPY_RECTS
  268. SurfaceClass *background=m_backgroundTexture->Get_Surface_Level();
  269. background->Get_Description(surface_desc);
  270. #else
  271. D3DSURFACE_DESC D3DDesc;
  272. IDirect3DTexture8 *backTexture=W3DShaderManager::getRenderTexture();
  273. if (!backTexture || !W3DShaderManager::isRenderingToTexture())
  274. return; //this card doesn't support render targets.
  275. backTexture->GetLevelDesc(0,&D3DDesc);
  276. surface_desc.Width = D3DDesc.Width;
  277. surface_desc.Height = D3DDesc.Height;
  278. #endif
  279. Real texClampX = (Real)TheTacticalView->getWidth()/(Real)surface_desc.Width;
  280. Real texClampY = (Real)TheTacticalView->getHeight()/(Real)surface_desc.Height;
  281. Real texScaleX = texClampX*0.5f;
  282. Real texScaleY = texClampY*0.5f;
  283. //Do a first pass over the smudges to determine how many are visible
  284. //and to fill in their world-space positions and screen uv coordinates.
  285. //TODO: Optimize out this extra pass!
  286. //TODO: Find size of screen rectangle that actually needs copying.
  287. SmudgeSet *set=m_usedSmudgeSetList.Head(); //first set that didn't fit into render batch.
  288. Int count = 0;
  289. if (set)
  290. { //there are possibly some smudges to render, so make sure background particles have finished drawing.
  291. SortingRendererClass::Flush(); //draw sorted translucent polys like particles.
  292. }
  293. while (set)
  294. {
  295. Smudge *smudge=set->getUsedSmudgeList().Head();
  296. while (smudge)
  297. {
  298. //Get view-space center
  299. Matrix3D::Transform_Vector(view,smudge->m_pos,&vsVert);
  300. //Get 5 view-space vertices
  301. Smudge::smudgeVertex *verts=smudge->m_verts;
  302. //Do center vertex outside 'for' loop since it's different.
  303. verts[4].pos = vsVert;
  304. for (Int i=0; i<4; i++)
  305. {
  306. verts[i].pos = vsVert + vertex_offsets[i] * smudge->m_size;
  307. //Ge uv coordinates for each vertex
  308. ssVert = proj * verts[i].pos;
  309. Real oow = 1.0f/ssVert.W;
  310. ssVert *= oow; //returned in camera space which is -1,-1 (bottom-left) to 1,1 (top-right)
  311. //convert camera space to uv space: 0,0 (top-left), 1,1 (bottom-right)
  312. verts[i].uv.Set((ssVert.X+1.0f)*texScaleX,(1.0f-ssVert.Y)*texScaleY);
  313. Vector2 &thisUV=verts[i].uv;
  314. //Clamp coordinates so we're not referencing texels outside the view.
  315. if (thisUV.X > texClampX)
  316. smudge->m_offset.X = 0;
  317. else
  318. if (thisUV.X < 0)
  319. smudge->m_offset.X = 0;
  320. if (thisUV.Y > texClampY)
  321. smudge->m_offset.Y = 0;
  322. else
  323. if (thisUV.Y < 0)
  324. smudge->m_offset.Y = 0;
  325. }
  326. //Finish center vertex
  327. //Ge uv coordinates by interpolating corner uv coordinates and applying desired offset.
  328. uvSpanX=verts[3].uv.X - verts[0].uv.X;
  329. uvSpanY=verts[1].uv.Y - verts[0].uv.Y;
  330. verts[4].uv.X=verts[0].uv.X+uvSpanX*(0.5f+smudge->m_offset.X);
  331. verts[4].uv.Y=verts[0].uv.Y+uvSpanY*(0.5f+smudge->m_offset.X);
  332. count++; //increment visible smudge count.
  333. smudge=smudge->Succ();
  334. }
  335. set=set->Succ(); //advance to next node.
  336. }
  337. if (!count)
  338. {
  339. #ifdef USE_COPY_RECTS
  340. REF_PTR_RELEASE(background);
  341. #endif
  342. return; //nothing to render.
  343. }
  344. #ifdef USE_COPY_RECTS
  345. SurfaceClass *backBuffer=DX8Wrapper::_Get_DX8_Back_Buffer();
  346. backBuffer->Get_Description(surface_desc);
  347. //Copy the area of backbuffer occupied by smudges into an alternate buffer.
  348. background->Copy(0,0,0,0,surface_desc.Width,surface_desc.Height,backBuffer);
  349. REF_PTR_RELEASE(background);
  350. REF_PTR_RELEASE(backBuffer);
  351. #endif
  352. Matrix4x4 identity(true);
  353. DX8Wrapper::Set_Transform(D3DTS_WORLD,identity);
  354. DX8Wrapper::Set_Transform(D3DTS_VIEW,identity);
  355. DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0);
  356. //DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueSpriteShader);
  357. DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader);
  358. #ifdef USE_COPY_RECTS
  359. DX8Wrapper::Set_Texture(0,m_backgroundTexture);
  360. #else
  361. DX8Wrapper::Set_DX8_Texture(0,backTexture);
  362. //Need these states in case texture is non-power-of-2
  363. DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
  364. DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
  365. DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSW, D3DTADDRESS_CLAMP);
  366. DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
  367. DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
  368. DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MIPFILTER, D3DTEXF_NONE);
  369. #endif
  370. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE);
  371. DX8Wrapper::Set_Material(vmat);
  372. REF_PTR_RELEASE(vmat);
  373. DX8Wrapper::Apply_Render_State_Changes();
  374. //Disable reading texture alpha since it's undefined.
  375. //DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);
  376. DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG2);
  377. Int smudgesRemaining=count;
  378. set=m_usedSmudgeSetList.Head(); //first smudge set that needs rendering.
  379. Smudge *remainingSmudgeStart=set->getUsedSmudgeList().Head(); //first smudge that needs rendering.
  380. while (smudgesRemaining) //keep drawing smudges until we run out.
  381. {
  382. //Now that we know how many smudges need rendering, allocate vertex buffer space and copy verts.
  383. count=smudgesRemaining;
  384. if (count > SMUDGE_DRAW_SIZE)
  385. count = SMUDGE_DRAW_SIZE;
  386. Int smudgesInRenderBatch=0;
  387. DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,count*5); //allocate 5 verts per smudge.
  388. {
  389. DynamicVBAccessClass::WriteLockClass lock(&vb_access);
  390. VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array();
  391. while (set)
  392. {
  393. Smudge *smudge=remainingSmudgeStart;
  394. while (smudge)
  395. {
  396. Smudge::smudgeVertex *smVerts = smudge->m_verts;
  397. //Check if we exceeded maximum number of smudges allowed per draw call.
  398. if (smudgesInRenderBatch >= count)
  399. { remainingSmudgeStart = smudge;
  400. goto flushSmudges;
  401. }
  402. //Set center vertex opacity.
  403. vertexDiffuse[4] = ((Int)(smudge->m_opacity * 255.0f) << 24) | THE_COLOR;
  404. for (Int i=0; i<5; i++)
  405. {
  406. verts->x=smVerts->pos.X;
  407. verts->y=smVerts->pos.Y;
  408. verts->z=smVerts->pos.Z;
  409. verts->nx=0; //keep AGP write-combining active
  410. verts->ny=0;
  411. verts->nz=0;
  412. verts->diffuse=vertexDiffuse[i]; //set to transparent
  413. verts->u1=smVerts->uv.X;
  414. verts->v1=smVerts->uv.Y;
  415. verts->u2=0; //keep AGP write-combining active
  416. verts->v2=0;
  417. verts++;
  418. smVerts++;
  419. }
  420. smudgesInRenderBatch++;
  421. smudge=smudge->Succ();
  422. }
  423. set=set->Succ(); //advance to next node.
  424. if (set) //start next batch at beginning of set.
  425. remainingSmudgeStart = set->getUsedSmudgeList().Head();
  426. }
  427. flushSmudges:
  428. DX8Wrapper::Set_Vertex_Buffer(vb_access);
  429. }
  430. DX8Wrapper::Draw_Triangles( 0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5);
  431. //Debug Code which draws outline around smudge
  432. /* DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
  433. DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
  434. DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG2);
  435. DX8Wrapper::Draw_Triangles( 0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5);
  436. DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);
  437. DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
  438. DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);
  439. */
  440. smudgesRemaining -= smudgesInRenderBatch;
  441. }
  442. DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_MODULATE);
  443. DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE);
  444. }