1 /*
2   LICENSE
3   -------
4 Copyright 2005-2013 Nullsoft, Inc.
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9 
10   * Redistributions of source code must retain the above copyright notice,
11     this list of conditions and the following disclaimer.
12 
13   * Redistributions in binary form must reproduce the above copyright notice,
14     this list of conditions and the following disclaimer in the documentation
15     and/or other materials provided with the distribution.
16 
17   * Neither the name of Nullsoft nor the names of its contributors may be used to
18     endorse or promote products derived from this software without specific prior written permission.
19 
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
21 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
22 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
26 IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
27 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 
30 #include "textmgr.h"
31 #include "support.h"
32 #include "utility.h"
33 
34 #define MAX_MSG_CHARS (65536*2)
35 #define SafeRelease(x) { if (x) {x->Release(); x=NULL;} }
36 wchar_t g_szMsgPool[2][MAX_MSG_CHARS];
37 
38 /*
39     NOTES ON CTextManager
40 
41     *** -desktop mode was SLOOOW when songtitles are on!, esp. since anim. songtitles...
42         -> decided to cache output of ID3DXFont by rendering to a (vidmem) texture,
43            ** only when things change. **  That became CTextManager.
44     -uses GDI-based ID3DXFont to draw text to a 2nd (VIDEO MEMORY) surface,
45         but each frame, it only draws what is necessary (what's changed
46         since last frame).  It then blits that image (additively) to
47         the back buffer each frame.  (note that dark boxes wouldn't work
48         w/additive drawing, since they're black, so those have to be
49         manually drawn (as black boxes) by the plugin shell, AS WELL AS
50         entered into the CTextManager queue as dark boxes, to handle
51         erasure, dirty rectangles, etc.)
52 
53     PROS/CONS:
54     (+) Supports all GDI features: italics, kerning, international fonts, formatting, &, etc.
55     (-) takes a lot of memory
56     (-) if texture can't be created @ proper size, fonts will appear too big
57             -> so don't use texture at all, in that case.
58             -> at least this way it will work well on all newer cards [w/memory]
59     (-) it's still going to crawl *when the text changes*,
60         because d3dx will upload textures to vidmem & blit them *once for each change*.
61 
62     OTHER CONCERNS/KIV:
63     -what if m_lpDDSText can't be created @ actual size of window?
64         If it's bigger, that's ok; but if it's smaller, that should result
65         in a clipped area for the text - hmm....
66 */
67 
CTextManager()68 CTextManager::CTextManager()
69 {
70 }
71 
~CTextManager()72 CTextManager::~CTextManager()
73 {
74 }
75 
Init(LPDIRECT3DDEVICE9 lpDevice,IDirect3DTexture9 * lpTextSurface,int bAdditive)76 void CTextManager::Init(LPDIRECT3DDEVICE9 lpDevice, IDirect3DTexture9* lpTextSurface, int bAdditive)
77 {
78     m_lpDevice = lpDevice;
79     m_lpTextSurface = lpTextSurface;
80     m_blit_additively = bAdditive;
81 
82     m_b = 0;
83     m_nMsg[0] = 0;
84     m_nMsg[1] = 0;
85     m_next_msg_start_ptr = g_szMsgPool[m_b];
86 }
87 
Finish()88 void CTextManager::Finish()
89 {
90 }
91 
ClearAll()92 void CTextManager::ClearAll()
93 {
94     m_nMsg[m_b] = 0;
95     m_next_msg_start_ptr = g_szMsgPool[m_b];
96 }
97 
DrawBox(LPRECT pRect,DWORD boxColor)98 void CTextManager::DrawBox(LPRECT pRect, DWORD boxColor)
99 {
100     if (!pRect)
101         return;
102 
103     if ((m_nMsg[m_b] < MAX_MSGS) &&
104         (DWORD)m_next_msg_start_ptr - (DWORD)g_szMsgPool[m_b] + 0 + 1 < MAX_MSG_CHARS)
105     {
106         *m_next_msg_start_ptr = 0;
107 
108         m_msg[m_b][m_nMsg[m_b]].msg   = m_next_msg_start_ptr;
109         m_msg[m_b][m_nMsg[m_b]].pfont = NULL;
110         m_msg[m_b][m_nMsg[m_b]].rect  = *pRect;
111         m_msg[m_b][m_nMsg[m_b]].flags = 0;
112         m_msg[m_b][m_nMsg[m_b]].color = 0xFFFFFFFF;
113         m_msg[m_b][m_nMsg[m_b]].bgColor = boxColor;
114         m_nMsg[m_b]++;
115         m_next_msg_start_ptr += 1;
116     }
117 }
118 
DrawText(LPD3DXFONT pFont,char * szText,RECT * pRect,DWORD flags,DWORD color,bool bBox,DWORD boxColor)119 int CTextManager::DrawText(LPD3DXFONT pFont, char* szText, RECT* pRect, DWORD flags, DWORD color, bool bBox, DWORD boxColor)
120 {
121     // these aren't supported by D3DX9:
122     flags &= ~(DT_WORD_ELLIPSIS | DT_END_ELLIPSIS | DT_NOPREFIX);
123 
124     if (!(pFont && pRect && szText))
125         return 0;
126 
127     if (flags & DT_CALCRECT)
128         return pFont->DrawText(NULL, szText, -1, pRect, flags, color);
129 
130     if (!m_lpDevice /*|| !m_lpTextSurface*/)
131         return 0;
132 
133     int len = strlen(szText);
134 
135     if ((m_nMsg[m_b] < MAX_MSGS) &&
136         (DWORD)m_next_msg_start_ptr - (DWORD)g_szMsgPool[m_b] + len + 1 < MAX_MSG_CHARS)
137     {
138         wcscpy(m_next_msg_start_ptr, AutoWide(szText));
139 
140         m_msg[m_b][m_nMsg[m_b]].msg   = m_next_msg_start_ptr;
141         m_msg[m_b][m_nMsg[m_b]].pfont = pFont;
142         m_msg[m_b][m_nMsg[m_b]].rect  = *pRect;
143         m_msg[m_b][m_nMsg[m_b]].flags = flags;
144         m_msg[m_b][m_nMsg[m_b]].color = color;
145         m_msg[m_b][m_nMsg[m_b]].bgColor = boxColor;
146 
147         // shrink rects on new frame's text strings; important for deletions
148         int h = pFont->DrawText(NULL, szText, len, &m_msg[m_b][m_nMsg[m_b]].rect, flags | DT_CALCRECT, color);
149 
150         m_nMsg[m_b]++;
151         m_next_msg_start_ptr += len + 1;
152 
153         if (bBox)
154         {
155             // adds a message with no text, but the rect is the same as the text, so it creates a black box
156             DrawBox(&m_msg[m_b][m_nMsg[m_b]-1].rect, boxColor);
157             // now swap it with the text that precedes it, so it draws first, and becomes a background
158             td_string x = m_msg[m_b][m_nMsg[m_b]-1];
159             m_msg[m_b][m_nMsg[m_b]-1] = m_msg[m_b][m_nMsg[m_b]-2];
160             m_msg[m_b][m_nMsg[m_b]-2] = x;
161         }
162         return h;
163     }
164 
165     // no room for more text? ok, but still return accurate info:
166     RECT r2 = *pRect;
167     int h = pFont->DrawText(NULL, szText, len, &r2, flags | DT_CALCRECT, color);
168     return h;
169 }
170 
DrawTextW(LPD3DXFONT pFont,wchar_t * szText,RECT * pRect,DWORD flags,DWORD color,bool bBox,DWORD boxColor)171 int CTextManager::DrawTextW(LPD3DXFONT pFont, wchar_t* szText, RECT* pRect, DWORD flags, DWORD color, bool bBox, DWORD boxColor)
172 {
173     // these aren't supported by D3DX9:
174     flags &= ~(DT_WORD_ELLIPSIS | DT_END_ELLIPSIS | DT_NOPREFIX);
175 
176     if (!(pFont && pRect && szText))
177         return 0;
178 
179     if (flags & DT_CALCRECT)
180         return pFont->DrawTextW(NULL, szText, -1, pRect, flags, color);
181 
182     if (!m_lpDevice /*|| !m_lpTextSurface*/)
183         return 0;
184 
185     int len = wcslen(szText);
186 
187     if ((m_nMsg[m_b] < MAX_MSGS) &&
188         (DWORD)m_next_msg_start_ptr - (DWORD)g_szMsgPool[m_b] + len + 1 < MAX_MSG_CHARS)
189     {
190         wcscpy(m_next_msg_start_ptr, szText);
191 
192         m_msg[m_b][m_nMsg[m_b]].msg   = m_next_msg_start_ptr;
193         m_msg[m_b][m_nMsg[m_b]].pfont = pFont;
194         m_msg[m_b][m_nMsg[m_b]].rect  = *pRect;
195         m_msg[m_b][m_nMsg[m_b]].flags = flags;
196         m_msg[m_b][m_nMsg[m_b]].color = color;
197         m_msg[m_b][m_nMsg[m_b]].bgColor = boxColor;
198 
199         // shrink rects on new frame's text strings; important for deletions
200         int h = pFont->DrawTextW(NULL, szText, len, &m_msg[m_b][m_nMsg[m_b]].rect, flags | DT_CALCRECT, color);
201 
202         m_nMsg[m_b]++;
203         m_next_msg_start_ptr += len + 1;
204 
205         if (bBox)
206         {
207             // adds a message with no text, but the rect is the same as the text, so it creates a black box
208             DrawBox(&m_msg[m_b][m_nMsg[m_b]-1].rect, boxColor);
209             // now swap it with the text that precedes it, so it draws first, and becomes a background
210             td_string x = m_msg[m_b][m_nMsg[m_b]-1];
211             m_msg[m_b][m_nMsg[m_b]-1] = m_msg[m_b][m_nMsg[m_b]-2];
212             m_msg[m_b][m_nMsg[m_b]-2] = x;
213         }
214         return h;
215     }
216 
217     // no room for more text? ok, but still return accurate info:
218     RECT r2 = *pRect;
219     int h = pFont->DrawTextW(NULL, szText, len, &r2, flags | DT_CALCRECT, color);
220     return h;
221 }
222 
223 #define MATCH(i,j) ( m_msg[m_b][i].pfont == m_msg[1-m_b][j].pfont && \
224                      m_msg[m_b][i].flags == m_msg[1-m_b][j].flags && \
225                      m_msg[m_b][i].color == m_msg[1-m_b][j].color && \
226                      m_msg[m_b][i].bgColor == m_msg[1-m_b][j].bgColor && \
227                      memcmp(&m_msg[m_b][i].rect, &m_msg[1-m_b][j].rect, sizeof(RECT))==0 && \
228                      wcscmp(m_msg[m_b][i].msg, m_msg[1-m_b][j].msg)==0 )
229 
DrawNow()230 void CTextManager::DrawNow()
231 {
232     if (!m_lpDevice)
233         return;
234 
235     if (m_nMsg[m_b] > 0 || m_nMsg[1-m_b] > 0)  // second condition req'd for clearing text in VJ mode
236     {
237         D3DXMATRIX Ortho2D;
238         pMatrixOrthoLH(&Ortho2D, 2.0f, -2.0f, 0.0f, 1.0f);
239         m_lpDevice->SetTransform(D3DTS_PROJECTION, &Ortho2D);
240 
241         #define NUM_DIRTY_RECTS 3
242         RECT dirty_rect[NUM_DIRTY_RECTS];
243         int dirty_rects_ready = 0;
244 
245         int bRTT = (m_lpTextSurface==NULL) ? 0 : 1;
246         LPDIRECT3DSURFACE9 pBackBuffer=NULL;//, pZBuffer=NULL;
247         D3DSURFACE_DESC desc_backbuf, desc_text_surface;
248 
249         // clear added/deleted flags
250         void* last_dark_box = NULL;
251         for (int i=0; i<m_nMsg[m_b]; i++)
252         {
253             m_msg[m_b][i].deleted = m_msg[m_b][i].added = 0;
254             m_msg[m_b][i].prev_dark_box_ptr = last_dark_box;
255             last_dark_box = (m_msg[m_b][i].pfont) ? last_dark_box : (void*)&m_msg[m_b][i];
256         }
257         last_dark_box = NULL;
258         for (int j=0; j<m_nMsg[1-m_b]; j++)
259         {
260             m_msg[1-m_b][j].deleted = m_msg[1-m_b][j].added = 0;
261             m_msg[1-m_b][j].prev_dark_box_ptr = last_dark_box;
262             last_dark_box = (m_msg[1-m_b][j].pfont) ? last_dark_box : (void*)&m_msg[1-m_b][j];
263         }
264 
265         int bRedrawText = 0;
266         if (!bRTT || (m_nMsg[m_b]>0 && m_nMsg[1-m_b]==0))
267         {
268             bRedrawText = 2;    // redraw ALL
269         }
270         else
271         {
272             // try to synchronize the text strings from last frame + this frame,
273             // and label additions & deletions.  algorithm will catch:
274             //      -insertion of any # of items in one spot
275             //      -deletion of any # of items from one spot
276             //      -changes to 1 item
277             //      -changes to 2 consecutive items
278             // (provided that the 2 text strings immediately bounding the
279             //  additions/deletions/change(s) are left unchanged.)
280             // in any other case, all the text is just re-rendered.
281 
282             int i = 0;
283             int j = 0;
284             while (i < m_nMsg[m_b] && j < m_nMsg[1-m_b])
285             {
286                 // MATCH macro: first idx is record # for current stuff; second idx is record # for prev frame stuff.
287                 if (MATCH(i,j))
288                 {
289                     i++;
290                     j++;
291                 }
292                 else
293                 {
294                     int continue_now = 0;
295 
296                     // scan to see if something was added:
297                     for (int i2=i+1; i2<m_nMsg[m_b]; i2++)
298                         if (MATCH(i2,j))
299                         {
300                             for (int i3=i; i3<i2; i3++)
301                                 m_msg[m_b][i3].added = 1;
302                             i = i2;
303                             bRedrawText = 1;
304                             continue_now = 1;
305                             break;
306                         }
307                     if (continue_now)
308                         continue;
309 
310                     // scan to see if something was deleted:
311                     for (int j2=j+1; j2<m_nMsg[1-m_b]; j2++)
312                         if (MATCH(i,j2))
313                         {
314                             for (int j3=j; j3<j2; j3++)
315                                 m_msg[1-m_b][j3].deleted = 1;
316                             j = j2;
317                             bRedrawText = 1;
318                             continue_now = 1;
319                             break;
320                         }
321                     if (continue_now)
322                         continue;
323 
324                     // scan to see if just a small group of 1-4 items were changed
325                     // [and are followed by two identical items again]
326                     int break_now = 0;
327                     for (int chgd=1; chgd<=4; chgd++)
328                     {
329                         if (i>=m_nMsg[m_b]-chgd || j>=m_nMsg[1-m_b]-chgd)
330                         {
331                             // only a few items left in one of the lists -> just finish it
332                             bRedrawText = 1;
333                             break_now = 1;
334                             break;
335                         }
336                         if (i<m_nMsg[m_b]-chgd && j<m_nMsg[1-m_b]-chgd && MATCH(i+chgd, j+chgd))
337                         {
338                             for (int k=0; k<chgd; k++)
339                             {
340                                 m_msg[  m_b][i+k].added   = 1;
341                                 m_msg[1-m_b][j+k].deleted = 1;
342                             }
343                             i += chgd;
344                             j += chgd;
345 
346                             bRedrawText = 1;
347                             continue_now = 1;
348                             break;
349                         }
350                     }
351                     if (break_now)
352                         break;
353                     if (continue_now)
354                         continue;
355 
356                     // otherwise, nontrivial case -> just re-render whole thing
357                     bRedrawText = 2;    // redraw ALL
358                     break;
359                 }
360             }
361 
362             if (bRedrawText < 2)
363             {
364                 while (i < m_nMsg[m_b])
365                 {
366                     m_msg[m_b][i].added = 1;
367                     bRedrawText = 1;
368                     i++;
369                 }
370 
371                 while (j < m_nMsg[1-m_b])
372                 {
373                     m_msg[1-m_b][j].deleted = 1;
374                     bRedrawText = 1;
375                     j++;
376                 }
377             }
378         }
379 
380         // ------------------------------------------------------------
381 
382         // 0. remember old render target & get surface descriptions
383         m_lpDevice->GetRenderTarget( 0, &pBackBuffer );
384         pBackBuffer->GetDesc(&desc_backbuf);
385 
386         if (bRTT)
387         {
388             //if (m_lpDevice->GetDepthStencilSurface( &pZBuffer ) != D3D_OK)
389             //    pZBuffer = NULL;                  // ok if return val != D3D_OK - just means there is no zbuffer.
390             if (m_lpTextSurface->GetLevelDesc(0, &desc_text_surface) != D3D_OK)
391                 bRTT = 0;
392 
393             m_lpDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE );
394             m_lpDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
395             m_lpDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT );
396             m_lpDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE );
397 
398             m_lpDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 );
399             m_lpDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE );
400             m_lpDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE );
401 
402             m_lpDevice->SetRenderState( D3DRS_ZENABLE, FALSE );
403         }
404         else
405         {
406             desc_text_surface = desc_backbuf;
407         }
408 
409         if (bRTT && bRedrawText)
410         do
411         {
412             // 1. change render target
413 	        m_lpDevice->SetTexture(0, NULL);
414 
415             IDirect3DSurface9* pNewTarget = NULL;
416             if (m_lpTextSurface->GetSurfaceLevel(0, &pNewTarget) != D3D_OK)
417             {
418                 bRTT = 0;
419                 break;
420             }
421             if (m_lpDevice->SetRenderTarget(0, pNewTarget) != D3D_OK)
422             {
423                 pNewTarget->Release();
424                 bRTT = 0;
425                 break;
426             }
427 			//m_lpDevice->SetDepthStencilSurface( ??? );
428             pNewTarget->Release();
429 
430 	        m_lpDevice->SetTexture(0, NULL);
431 
432             // 2. clear to black
433 	        //m_lpDevice->SetTexture(0, NULL);
434 	        m_lpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
435             m_lpDevice->SetVertexShader( NULL );
436             m_lpDevice->SetFVF( WFVERTEX_FORMAT );
437             m_lpDevice->SetPixelShader( NULL );
438 	        WFVERTEX v3[4];
439             if (bRedrawText==2)
440             {
441                 DWORD clearcolor = m_msg[m_b][j].bgColor;//0xFF000000;// | ((rand()%32)<<16) | ((rand()%32)<<8) | ((rand()%32));
442                 for (int i=0; i<4; i++)
443                 {
444                     v3[i].x = -1.0f + 2.0f*(i%2);
445                     v3[i].y = -1.0f + 2.0f*(i/2);
446                     v3[i].z = 0;
447                     v3[i].Diffuse = clearcolor;
448                 }
449                 m_lpDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v3, sizeof(WFVERTEX));
450             }
451             else
452             {
453                 // 1. erase (draw black box over) any old text items deleted.
454                 // also, update the dirty rects; stuff that was ABOVE/BELOW these guys will need redrawn!
455                 //   (..picture them staggered)
456                 for (int j=0; j<m_nMsg[1-m_b]; j++)
457                 {
458                     // erase text from PREV frame if it was deleted.
459                     if (m_msg[1-m_b][j].deleted)
460                     {
461                         float x0 = -1.0f + 2.0f*m_msg[1-m_b][j].rect.left/(float)desc_text_surface.Width;
462                         float x1 = -1.0f + 2.0f*m_msg[1-m_b][j].rect.right/(float)desc_text_surface.Width;
463                         float y0 = -1.0f + 2.0f*m_msg[1-m_b][j].rect.top/(float)desc_text_surface.Height;
464                         float y1 = -1.0f + 2.0f*m_msg[1-m_b][j].rect.bottom/(float)desc_text_surface.Height;
465                         for (int i=0; i<4; i++)
466                         {
467                             v3[i].x = (i%2) ? x0 : x1;
468                             v3[i].y = (i/2) ? y0 : y1;
469                             v3[i].z = 0;
470                             v3[i].Diffuse = m_msg[m_b][j].bgColor;//0xFF000000;//0xFF300000;
471                         }
472                         m_lpDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v3, sizeof(WFVERTEX));
473 
474                         //----------------------------------
475 
476                         // special case:
477                         //   if something is erased, but it's totally inside a dark box,
478                         //   then don't add it to the dirty rectangle.
479                         td_string* pDarkBox = (td_string*)m_msg[1-m_b][j].prev_dark_box_ptr;
480                         int add_to_dirty_rect = 1;
481                         while (pDarkBox && add_to_dirty_rect)
482                         {
483                             RECT t;
484                             UnionRect(&t, &pDarkBox->rect, &m_msg[1-m_b][j].rect);
485                             if (EqualRect(&t, &pDarkBox->rect))
486                                 add_to_dirty_rect = 0;
487                             pDarkBox = (td_string*)pDarkBox->prev_dark_box_ptr;
488                         }
489 
490                         // also, update dirty rects
491                         // first, check to see if this shares area or a border w/any of the going dirty rects,
492                         // and if so, expand that dirty rect.
493                         if (add_to_dirty_rect)
494                         {
495                             int done = 0;
496                             RECT t;
497                             RECT r1 = m_msg[1-m_b][j].rect;
498                             RECT r2 = m_msg[1-m_b][j].rect;
499                             r2.top -= 1;
500                             r2.left -= 1;
501                             r2.right += 1;
502                             r2.bottom += 1;
503                             for (i=0; i<dirty_rects_ready; i++)
504                             {
505                                 if (IntersectRect(&t, &r2, &dirty_rect[i]))
506                                 {
507                                     // expand the dirty rect to include r1
508                                     UnionRect(&t, &r1, &dirty_rect[i]);
509                                     dirty_rect[i] = t;
510                                     done = 1;
511                                     break;
512                                 }
513                             }
514                             if (done==1) continue;
515 
516                             // if it's in a new spot, and there are still unused dirty rects, use those
517                             if (dirty_rects_ready < NUM_DIRTY_RECTS)
518                             {
519                                 dirty_rect[dirty_rects_ready] = r1;
520                                 dirty_rects_ready++;
521                                 continue;
522                             }
523 
524                             // otherwise, find the closest dirty rect...
525                             float nearest_dist;
526                             int nearest_id;
527                             for (i=0; i<NUM_DIRTY_RECTS; i++)
528                             {
529                                 int dx=0, dy=0;
530 
531                                 if      (r1.left > dirty_rect[i].right)
532                                     dx = r1.left - dirty_rect[i].right;
533                                 else if (dirty_rect[i].left > r1.right)
534                                     dx = dirty_rect[i].left - r1.right;
535 
536                                 if      (r1.top > dirty_rect[i].bottom)
537                                     dy = r1.top - dirty_rect[i].bottom;
538                                 else if (dirty_rect[i].top > r1.bottom)
539                                     dy = dirty_rect[i].top - r1.bottom;
540 
541                                 float dist = sqrtf((float)(dx*dx + dy*dy));
542                                 if (i==0 || dist < nearest_dist)
543                                 {
544                                     nearest_dist = dist;
545                                     nearest_id   = i;
546                                 }
547                             }
548                             //...and expand it to include this one.
549                             UnionRect(&t, &r1, &dirty_rect[nearest_id]);
550                             dirty_rect[nearest_id] = t;
551                         }
552                     }
553                 }
554 
555                 // 2. erase AND REDRAW any of *this* frame's text that falls in dirty rects
556                 //     from erasures of *prev* frame's deleted text:
557                 for (j=0; j<m_nMsg[m_b]; j++)
558                 {
559                     RECT t;
560                     // note: none of these could be 'deleted' status yet.
561                     if (!m_msg[m_b][j].added)
562                     {
563                         // check vs. dirty rects so far; if intersects any, erase + redraw this one.
564                         for (i=0; i<dirty_rects_ready; i++)
565                             if (m_msg[m_b][j].pfont &&   // exclude dark boxes... //fixme?
566                                 IntersectRect(&t, &dirty_rect[i], &m_msg[m_b][j].rect))
567                             {
568                                 float x0 = -1.0f + 2.0f*m_msg[m_b][j].rect.left/(float)desc_text_surface.Width;
569                                 float x1 = -1.0f + 2.0f*m_msg[m_b][j].rect.right/(float)desc_text_surface.Width;
570                                 float y0 = -1.0f + 2.0f*m_msg[m_b][j].rect.top/(float)desc_text_surface.Height;
571                                 float y1 = -1.0f + 2.0f*m_msg[m_b][j].rect.bottom/(float)desc_text_surface.Height;
572                                 for (int i=0; i<4; i++)
573                                 {
574                                     v3[i].x = (i%2) ? x0 : x1;
575                                     v3[i].y = (i/2) ? y0 : y1;
576                                     v3[i].z = 0;
577                                     v3[i].Diffuse = m_msg[m_b][j].bgColor;//0xFF000000;//0xFF000030;
578                                 }
579                                 m_lpDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v3, sizeof(WFVERTEX));
580 
581                                 m_msg[m_b][j].deleted = 1;
582                                 m_msg[m_b][j].added   = 1;
583                                 bRedrawText = 1;
584                             }
585                     }
586                 }
587             }
588         }
589         while (0);
590 
591         // 3. render text to TEXT surface
592         if (bRedrawText)
593         {
594 	        m_lpDevice->SetTexture(0, NULL);
595 	        m_lpDevice->SetTexture(1, NULL);
596             m_lpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
597             m_lpDevice->SetVertexShader( NULL );
598             m_lpDevice->SetPixelShader( NULL );
599 			m_lpDevice->SetFVF( WFVERTEX_FORMAT );
600 
601             for (int i=0; i<m_nMsg[m_b]; i++)
602                 if (bRedrawText==2 || m_msg[m_b][i].added==1)
603                     if (m_msg[m_b][i].pfont) // dark boxes have pfont==NULL
604                         // warning: in DX9, the DT_WORD_ELLIPSIS and DT_NOPREFIX flags cause no text to render!!
605                         m_msg[m_b][i].pfont->DrawTextW(NULL, m_msg[m_b][i].msg, -1, &m_msg[m_b][i].rect, m_msg[m_b][i].flags, m_msg[m_b][i].color);
606                     else if (m_msg[m_b][i].added || bRedrawText==2 || !bRTT)
607                     {
608 	                    WFVERTEX v3[4];
609                         float x0 = -1.0f + 2.0f*m_msg[m_b][i].rect.left/(float)desc_text_surface.Width;
610                         float x1 = -1.0f + 2.0f*m_msg[m_b][i].rect.right/(float)desc_text_surface.Width;
611                         float y0 = -1.0f + 2.0f*m_msg[m_b][i].rect.top/(float)desc_text_surface.Height;
612                         float y1 = -1.0f + 2.0f*m_msg[m_b][i].rect.bottom/(float)desc_text_surface.Height;
613                         for (int k=0; k<4; k++)
614                         {
615                             v3[k].x = (k%2) ? x0 : x1;
616                             v3[k].y = (k/2) ? y0 : y1;
617                             v3[k].z = 0;
618                             v3[k].Diffuse = m_msg[m_b][i].bgColor;//0xFF303000;
619                         }
620                         m_lpDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v3, sizeof(WFVERTEX));
621                     }
622         }
623 
624         if (bRTT)
625         {
626             // 4. restore render target
627             if (bRedrawText)
628             {
629                 m_lpDevice->SetTexture(0, NULL);
630                 m_lpDevice->SetRenderTarget( 0, pBackBuffer );//, pZBuffer );
631 				//m_lpDevice->SetDepthStencilSurface( pZBuffer );
632             }
633 
634             // 5. blit text surface to backbuffer
635 	        m_lpDevice->SetTexture(0, m_lpTextSurface);
636             m_lpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, m_blit_additively ? TRUE : FALSE);
637             m_lpDevice->SetRenderState(D3DRS_SRCBLEND,  D3DBLEND_ONE);
638             m_lpDevice->SetRenderState(D3DRS_DESTBLEND, m_blit_additively ? D3DBLEND_ONE : D3DBLEND_ZERO);
639             m_lpDevice->SetVertexShader( NULL );
640             m_lpDevice->SetPixelShader( NULL );
641             m_lpDevice->SetFVF( SPRITEVERTEX_FORMAT );
642 
643 	        SPRITEVERTEX v3[4];
644 	        ZeroMemory(v3, sizeof(SPRITEVERTEX)*4);
645             float fx = desc_text_surface.Width  / (float)desc_backbuf.Width ;
646             float fy = desc_text_surface.Height / (float)desc_backbuf.Height;
647             for (int i=0; i<4; i++)
648             {
649                 v3[i].x = (i%2==0) ? -1 : -1 + 2*fx;
650                 v3[i].y = (i/2==0) ? -1 : -1 + 2*fy;
651                 v3[i].z = 0;
652                 v3[i].tu = ((i%2==0) ? 0.0f : 1.0f) + 0.5f/desc_text_surface.Width;  // FIXES BLURRY TEXT even when bilinear interp. is on (which can't be turned off on all cards!)
653                 v3[i].tv = ((i/2==0) ? 0.0f : 1.0f) + 0.5f/desc_text_surface.Height; // FIXES BLURRY TEXT even when bilinear interp. is on (which can't be turned off on all cards!)
654                 v3[i].Diffuse = 0xFFFFFFFF;
655             }
656 
657             DWORD oldblend[3];
658             //m_lpDevice->GetTextureStageState(0, D3DTSS_MAGFILTER, &oldblend[0]);
659             //m_lpDevice->GetTextureStageState(1, D3DTSS_MINFILTER, &oldblend[1]);
660             //m_lpDevice->GetTextureStageState(2, D3DTSS_MIPFILTER, &oldblend[2]);
661 			m_lpDevice->GetSamplerState(0, D3DSAMP_MAGFILTER, &oldblend[0]);
662 			m_lpDevice->GetSamplerState(1, D3DSAMP_MINFILTER, &oldblend[1]);
663 			m_lpDevice->GetSamplerState(2, D3DSAMP_MIPFILTER, &oldblend[2]);
664 			m_lpDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
665 	        m_lpDevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_POINT);
666 	        m_lpDevice->SetSamplerState(2, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
667 
668             m_lpDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v3, sizeof(SPRITEVERTEX));
669 
670             m_lpDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, oldblend[0]);
671             m_lpDevice->SetSamplerState(1, D3DSAMP_MINFILTER, oldblend[1]);
672             m_lpDevice->SetSamplerState(2, D3DSAMP_MIPFILTER, oldblend[2]);
673 
674 	        m_lpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
675         }
676 
677         SafeRelease(pBackBuffer);
678         //SafeRelease(pZBuffer);
679 
680 	    m_lpDevice->SetTexture(0, NULL);
681 	    m_lpDevice->SetTexture(1, NULL);
682         m_lpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
683         m_lpDevice->SetVertexShader( NULL );
684         m_lpDevice->SetPixelShader( NULL );
685         m_lpDevice->SetFVF( SPRITEVERTEX_FORMAT );
686 
687         //D3DXMATRIX ident;
688         //D3DXMatrixIdentity(&ident);
689         //m_lpDevice->SetTransform(D3DTS_PROJECTION, &ident);
690     }
691 
692     // flip:
693     m_b = 1 - m_b;
694 
695     ClearAll();
696 }