aboutsummaryrefslogtreecommitdiff
path: root/src/windows/native/sun/java2d/d3d/D3DTextRenderer.cpp
blob: ea9bb9a22701d9dffad23255b0120d660affce8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

#include <malloc.h>
#include <math.h>
#include <jlong.h>

#include "sun_java2d_d3d_D3DTextRenderer.h"
#include "sun_java2d_pipe_BufferedTextPipe.h"

#include "SurfaceData.h"
#include "D3DContext.h"
#include "D3DSurfaceData.h"
#include "D3DRenderQueue.h"
#include "D3DTextRenderer.h"
#include "D3DGlyphCache.h"
#include "AccelGlyphCache.h"
#include "fontscalerdefs.h"

/**
 * The current "glyph mode" state.  This variable is used to track the
 * codepath used to render a particular glyph.  This variable is reset to
 * MODE_NOT_INITED at the beginning of every call to D3DTR_DrawGlyphList().
 * As each glyph is rendered, the glyphMode variable is updated to reflect
 * the current mode, so if the current mode is the same as the mode used
 * to render the previous glyph, we can avoid doing costly setup operations
 * each time.
 */
typedef enum {
    MODE_NOT_INITED,
    MODE_USE_CACHE_GRAY,
    MODE_USE_CACHE_LCD,
    MODE_NO_CACHE_GRAY,
    MODE_NO_CACHE_LCD
} GlyphMode;
static GlyphMode glyphMode = MODE_NOT_INITED;

/**
 * The current bounds of the "cached destination" texture, in destination
 * coordinate space.  The width/height of these bounds will not exceed the
 * D3DTR_CACHED_DEST_WIDTH/HEIGHT values defined above.  These bounds are
 * only considered valid when the isCachedDestValid flag is JNI_TRUE.
 */
static SurfaceDataBounds cachedDestBounds;

/**
 * This flag indicates whether the "cached destination" texture contains
 * valid data.  This flag is reset to JNI_FALSE at the beginning of every
 * call to D3DTR_DrawGlyphList().  Once we copy valid destination data
 * into the cached texture, this flag is set to JNI_TRUE.  This way, we
 * can limit the number of times we need to copy destination data, which
 * is a very costly operation.
 */
static jboolean isCachedDestValid = JNI_FALSE;

/**
 * The bounds of the previously rendered LCD glyph, in destination
 * coordinate space.  We use these bounds to determine whether the glyph
 * currently being rendered overlaps the previously rendered glyph (i.e.
 * its bounding box intersects that of the previously rendered glyph).
 * If so, we need to re-read the destination area associated with that
 * previous glyph so that we can correctly blend with the actual
 * destination data.
 */
static SurfaceDataBounds previousGlyphBounds;

/**
 * Updates the gamma and inverse gamma values for the LCD text shader.
 */
static HRESULT
D3DTR_UpdateLCDTextContrast(D3DContext *d3dc, jint contrast)
{
    HRESULT res;
    IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();

    jfloat fcon = ((jfloat)contrast) / 100.0f;
    jfloat invgamma = fcon;
    jfloat gamma = 1.0f / invgamma;
    jfloat vals[4];

    // update the "invgamma" parameter of the shader program
    vals[0] = invgamma;
    vals[1] = invgamma;
    vals[2] = invgamma;
    vals[3] = 0.0f; // unused
    pd3dDevice->SetPixelShaderConstantF(1, vals, 1);

    // update the "gamma" parameter of the shader program
    vals[0] = gamma;
    vals[1] = gamma;
    vals[2] = gamma;
    vals[3] = 0.0f; // unused
    res = pd3dDevice->SetPixelShaderConstantF(2, vals, 1);

    return res;
}

/**
 * Updates the current gamma-adjusted source color ("src_adj") of the LCD
 * text shader program.  Note that we could calculate this value in the
 * shader (e.g. just as we do for "dst_adj"), but would be unnecessary work
 * (and a measurable performance hit, maybe around 5%) since this value is
 * constant over the entire glyph list.  So instead we just calculate the
 * gamma-adjusted value once and update the uniform parameter of the LCD
 * shader as needed.
 */
static HRESULT
D3DTR_UpdateLCDTextColor(D3DContext *d3dc, jint contrast)
{
    IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();
    jfloat gamma = ((jfloat)contrast) / 100.0f;
    jfloat clr[4];

    J2dTraceLn1(J2D_TRACE_INFO,
                "D3DTR_UpdateLCDTextColor: contrast=%d", contrast);

    /*
     * Note: Ideally we would update the "srcAdj" uniform parameter only
     * when there is a change in the source color.  Fortunately, the cost
     * of querying the current D3D color state and updating the uniform
     * value is quite small, and in the common case we only need to do this
     * once per GlyphList, so we gain little from trying to optimize too
     * eagerly here.
     */

    // get the current D3D primary color state
    jint color = d3dc->pVCacher->GetColor();
    clr[0] = (jfloat)((color >> 16) & 0xff) / 255.0f;
    clr[1] = (jfloat)((color >>  8) & 0xff) / 255.0f;
    clr[2] = (jfloat)((color >>  0) & 0xff) / 255.0f;
    clr[3] = 0.0f; // unused

    // gamma adjust the primary color
    clr[0] = (jfloat)pow(clr[0], gamma);
    clr[1] = (jfloat)pow(clr[1], gamma);
    clr[2] = (jfloat)pow(clr[2], gamma);

    // update the "srcAdj" parameter of the shader program with this value
    return pd3dDevice->SetPixelShaderConstantF(0, clr, 1);
}

/**
 * Enables the LCD text shader and updates any related state, such as the
 * gamma values.
 */
static HRESULT
D3DTR_EnableLCDGlyphModeState(D3DContext *d3dc, D3DSDOps *dstOps,
                              jboolean useCache, jint contrast)
{
    D3DResource *pGlyphTexRes, *pCachedDestTexRes;
    IDirect3DTexture9 *pGlyphTex, *pCachedDestTex;

    RETURN_STATUS_IF_NULL(dstOps->pResource, E_FAIL);

    HRESULT res = S_OK;
    if (useCache) {
        // glyph cache had been already initialized
        pGlyphTexRes = d3dc->GetLCDGlyphCache()->GetGlyphCacheTexture();
    } else {
        res = d3dc->GetResourceManager()->GetBlitTexture(&pGlyphTexRes);
    }
    RETURN_STATUS_IF_FAILED(res);

    pGlyphTex = pGlyphTexRes->GetTexture();

    res = d3dc->GetResourceManager()->
        GetCachedDestTexture(dstOps->pResource->GetDesc()->Format,
                             &pCachedDestTexRes);
    RETURN_STATUS_IF_FAILED(res);
    pCachedDestTex = pCachedDestTexRes->GetTexture();

    IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();
    D3DTEXTUREFILTERTYPE fhint =
        d3dc->IsTextureFilteringSupported(D3DTEXF_NONE) ?
        D3DTEXF_NONE : D3DTEXF_POINT;
    pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, fhint);
    pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, fhint);
    pd3dDevice->SetSamplerState(1, D3DSAMP_MAGFILTER, fhint);
    pd3dDevice->SetSamplerState(1, D3DSAMP_MINFILTER, fhint);
    d3dc->UpdateTextureColorState(D3DTA_TEXTURE, 1);

    // bind the texture containing glyph data to texture unit 0
    d3dc->SetTexture(pGlyphTex, 0);

    // bind the texture tile containing destination data to texture unit 1
    d3dc->SetTexture(pCachedDestTex, 1);

    // create/enable the LCD text shader
    res = d3dc->EnableLCDTextProgram();
    RETURN_STATUS_IF_FAILED(res);

    // update the current contrast settings (note: these change very rarely,
    // but it seems that D3D pixel shader registers aren't maintained as
    // part of the pixel shader instance, so we need to update these
    // everytime around in case another shader blew away the contents
    // of those registers)
    D3DTR_UpdateLCDTextContrast(d3dc, contrast);

    // update the current color settings
    return D3DTR_UpdateLCDTextColor(d3dc, contrast);
}

HRESULT
D3DTR_EnableGlyphVertexCache(D3DContext *d3dc)
{
    J2dTraceLn(J2D_TRACE_INFO, "D3DTR_EnableGlyphVertexCache");

    IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();
    D3DTEXTUREFILTERTYPE fhint =
        d3dc->IsTextureFilteringSupported(D3DTEXF_NONE) ?
        D3DTEXF_NONE : D3DTEXF_POINT;
    pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, fhint);
    pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, fhint);

    // glyph cache had been successfully initialized if we got here
    D3DResource *pGlyphCacheTexRes =
        d3dc->GetGrayscaleGlyphCache()->GetGlyphCacheTexture();
    return d3dc->SetTexture(pGlyphCacheTexRes->GetTexture(), 0);
}

HRESULT
D3DTR_DisableGlyphVertexCache(D3DContext *d3dc)
{
    J2dTraceLn(J2D_TRACE_INFO, "D3DTR_DisableGlyphVertexCache");

    return d3dc->SetTexture(NULL, 0);
}

/**
 * Disables any pending state associated with the current "glyph mode".
 */
static HRESULT
D3DTR_DisableGlyphModeState(D3DContext *d3dc)
{
    HRESULT res = S_OK;
    IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();

    switch (glyphMode) {
    case MODE_NO_CACHE_LCD:
    case MODE_USE_CACHE_LCD:
        d3dc->FlushVertexQueue();
        pd3dDevice->SetPixelShader(NULL);
        res = d3dc->SetTexture(NULL, 1);
        break;

    case MODE_NO_CACHE_GRAY:
    case MODE_USE_CACHE_GRAY:
    case MODE_NOT_INITED:
    default:
        break;
    }
    return res;
}

static HRESULT
D3DTR_DrawGrayscaleGlyphViaCache(D3DContext *d3dc,
                                 GlyphInfo *ginfo, jint x, jint y)
{
    HRESULT res = S_OK;
    D3DGlyphCache *pGrayscaleGCache;
    CacheCellInfo *cell;
    GlyphCacheInfo *gcache;
    jfloat x1, y1, x2, y2;

    J2dTraceLn(J2D_TRACE_VERBOSE, "D3DTR_DrawGrayscaleGlyphViaCache");

    if (glyphMode != MODE_USE_CACHE_GRAY) {
        D3DTR_DisableGlyphModeState(d3dc);

        res = d3dc->BeginScene(STATE_GLYPHOP);
        RETURN_STATUS_IF_FAILED(res);

        glyphMode = MODE_USE_CACHE_GRAY;
    }

    pGrayscaleGCache = d3dc->GetGrayscaleGlyphCache();
    gcache = pGrayscaleGCache->GetGlyphCache();
    cell = AccelGlyphCache_GetCellInfoForCache(ginfo, gcache);
    if (cell == NULL) {
        // attempt to add glyph to accelerated glyph cache
        res = pGrayscaleGCache->AddGlyph(ginfo);
        RETURN_STATUS_IF_FAILED(res);

        cell = AccelGlyphCache_GetCellInfoForCache(ginfo, gcache);
        RETURN_STATUS_IF_NULL(cell, E_FAIL);
    }

    cell->timesRendered++;

    x1 = (jfloat)x;
    y1 = (jfloat)y;
    x2 = x1 + ginfo->width;
    y2 = y1 + ginfo->height;

    return d3dc->pVCacher->DrawTexture(x1, y1, x2, y2,
                                       cell->tx1, cell->ty1,
                                       cell->tx2, cell->ty2);
}

/**
 * Evaluates to true if the rectangle defined by gx1/gy1/gx2/gy2 is
 * inside outerBounds.
 */
#define INSIDE(gx1, gy1, gx2, gy2, outerBounds) \
    (((gx1) >= outerBounds.x1) && ((gy1) >= outerBounds.y1) && \
     ((gx2) <= outerBounds.x2) && ((gy2) <= outerBounds.y2))

/**
 * Evaluates to true if the rectangle defined by gx1/gy1/gx2/gy2 intersects
 * the rectangle defined by bounds.
 */
#define INTERSECTS(gx1, gy1, gx2, gy2, bounds) \
    ((bounds.x2   > (gx1)) && (bounds.y2 > (gy1)) && \
     (bounds.x1   < (gx2)) && (bounds.y1 < (gy2)))

/**
 * This method checks to see if the given LCD glyph bounds fall within the
 * cached destination texture bounds.  If so, this method can return
 * immediately.  If not, this method will copy a chunk of framebuffer data
 * into the cached destination texture and then update the current cached
 * destination bounds before returning.
 *
 * The agx1, agx2 are "adjusted" glyph bounds, which are only used when checking
 * against the previous glyph bounds.
 */
static HRESULT
D3DTR_UpdateCachedDestination(D3DContext *d3dc, D3DSDOps *dstOps,
                              GlyphInfo *ginfo,
                              jint gx1, jint gy1, jint gx2, jint gy2,
                              jint agx1, jint agx2,
                              jint glyphIndex, jint totalGlyphs)
{
    jint dx1, dy1, dx2, dy2;
    D3DResource *pCachedDestTexRes;
    IDirect3DSurface9 *pCachedDestSurface, *pDst;
    HRESULT res;

    if (isCachedDestValid && INSIDE(gx1, gy1, gx2, gy2, cachedDestBounds)) {
        // glyph is already within the cached destination bounds; no need
        // to read back the entire destination region again, but we do
        // need to see if the current glyph overlaps the previous glyph...

        // only use the "adjusted" glyph bounds when checking against
        // previous glyph's bounds
        gx1 = agx1;
        gx2 = agx2;

        if (INTERSECTS(gx1, gy1, gx2, gy2, previousGlyphBounds)) {
            // the current glyph overlaps the destination region touched
            // by the previous glyph, so now we need to read back the part
            // of the destination corresponding to the previous glyph
            dx1 = previousGlyphBounds.x1;
            dy1 = previousGlyphBounds.y1;
            dx2 = previousGlyphBounds.x2;
            dy2 = previousGlyphBounds.y2;

            // REMIND: make sure we flush any pending primitives that are
            // dependent on the current contents of the cached dest
            d3dc->FlushVertexQueue();

            RETURN_STATUS_IF_NULL(dstOps->pResource, E_FAIL);
            RETURN_STATUS_IF_NULL(pDst = dstOps->pResource->GetSurface(),
                                  E_FAIL);
            res = d3dc->GetResourceManager()->
                GetCachedDestTexture(dstOps->pResource->GetDesc()->Format,
                                     &pCachedDestTexRes);
            RETURN_STATUS_IF_FAILED(res);
            pCachedDestSurface = pCachedDestTexRes->GetSurface();

            // now dxy12 represent the "desired" destination bounds, but the
            // StretchRect() call may fail if these fall outside the actual
            // surface bounds; therefore, we use cxy12 to represent the
            // clamped bounds, and dxy12 are saved for later
            jint cx1 = (dx1 < 0) ? 0 : dx1;
            jint cy1 = (dy1 < 0) ? 0 : dy1;
            jint cx2 = (dx2 > dstOps->width)  ? dstOps->width  : dx2;
            jint cy2 = (dy2 > dstOps->height) ? dstOps->height : dy2;

            if (cx2 > cx1 && cy2 > cy1) {
                // copy destination into subregion of cached texture tile
                //   cx1-cachedDestBounds.x1 == +xoffset from left of texture
                //   cy1-cachedDestBounds.y1 == +yoffset from top of texture
                //   cx2-cachedDestBounds.x1 == +xoffset from left of texture
                //   cy2-cachedDestBounds.y1 == +yoffset from top of texture
                jint cdx1 = cx1-cachedDestBounds.x1;
                jint cdy1 = cy1-cachedDestBounds.y1;
                jint cdx2 = cx2-cachedDestBounds.x1;
                jint cdy2 = cy2-cachedDestBounds.y1;
                RECT srcRect = {  cx1,  cy1,  cx2,  cy2 };
                RECT dstRect = { cdx1, cdy1, cdx2, cdy2 };

                IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();
                res = pd3dDevice->StretchRect(pDst, &srcRect,
                                              pCachedDestSurface, &dstRect,
                                              D3DTEXF_NONE);
            }
        }
    } else {
        // destination region is not valid, so we need to read back a
        // chunk of the destination into our cached texture

        // position the upper-left corner of the destination region on the
        // "top" line of glyph list
        // REMIND: this isn't ideal; it would be better if we had some idea
        //         of the bounding box of the whole glyph list (this is
        //         do-able, but would require iterating through the whole
        //         list up front, which may present its own problems)
        dx1 = gx1;
        dy1 = gy1;

        jint remainingWidth;
        if (ginfo->advanceX > 0) {
            // estimate the width based on our current position in the glyph
            // list and using the x advance of the current glyph (this is just
            // a quick and dirty heuristic; if this is a "thin" glyph image,
            // then we're likely to underestimate, and if it's "thick" then we
            // may end up reading back more than we need to)
            remainingWidth =
                (jint)(ginfo->advanceX * (totalGlyphs - glyphIndex));
            if (remainingWidth > D3DTR_CACHED_DEST_WIDTH) {
                remainingWidth = D3DTR_CACHED_DEST_WIDTH;
            } else if (remainingWidth < ginfo->width) {
                // in some cases, the x-advance may be slightly smaller
                // than the actual width of the glyph; if so, adjust our
                // estimate so that we can accomodate the entire glyph
                remainingWidth = ginfo->width;
            }
        } else {
            // a negative advance is possible when rendering rotated text,
            // in which case it is difficult to estimate an appropriate
            // region for readback, so we will pick a region that
            // encompasses just the current glyph
            remainingWidth = ginfo->width;
        }
        dx2 = dx1 + remainingWidth;

        // estimate the height (this is another sloppy heuristic; we'll
        // make the cached destination region tall enough to encompass most
        // glyphs that are small enough to fit in the glyph cache, and then
        // we add a little something extra to account for descenders
        dy2 = dy1 + D3DTR_CACHE_CELL_HEIGHT + 2;

        // REMIND: make sure we flush any pending primitives that are
        // dependent on the current contents of the cached dest
        d3dc->FlushVertexQueue();

        RETURN_STATUS_IF_NULL(dstOps->pResource, E_FAIL);
        RETURN_STATUS_IF_NULL(pDst = dstOps->pResource->GetSurface(), E_FAIL);
        res = d3dc->GetResourceManager()->
            GetCachedDestTexture(dstOps->pResource->GetDesc()->Format,
                                 &pCachedDestTexRes);
        RETURN_STATUS_IF_FAILED(res);
        pCachedDestSurface = pCachedDestTexRes->GetSurface();

        // now dxy12 represent the "desired" destination bounds, but the
        // StretchRect() call may fail if these fall outside the actual
        // surface bounds; therefore, we use cxy12 to represent the
        // clamped bounds, and dxy12 are saved for later
        jint cx1 = (dx1 < 0) ? 0 : dx1;
        jint cy1 = (dy1 < 0) ? 0 : dy1;
        jint cx2 = (dx2 > dstOps->width)  ? dstOps->width  : dx2;
        jint cy2 = (dy2 > dstOps->height) ? dstOps->height : dy2;

        if (cx2 > cx1 && cy2 > cy1) {
            // copy destination into cached texture tile (the upper-left
            // corner of the destination region will be positioned at the
            // upper-left corner (0,0) of the texture)
            RECT srcRect = { cx1, cy1, cx2, cy2 };
            RECT dstRect = { cx1-dx1, cy1-dy1, cx2-dx1, cy2-dy1 };

            IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();
            res = pd3dDevice->StretchRect(pDst, &srcRect,
                                          pCachedDestSurface, &dstRect,
                                          D3DTEXF_NONE);
        }

        // update the cached bounds and mark it valid
        cachedDestBounds.x1 = dx1;
        cachedDestBounds.y1 = dy1;
        cachedDestBounds.x2 = dx2;
        cachedDestBounds.y2 = dy2;
        isCachedDestValid = JNI_TRUE;
    }

    // always update the previous glyph bounds
    previousGlyphBounds.x1 = gx1;
    previousGlyphBounds.y1 = gy1;
    previousGlyphBounds.x2 = gx2;
    previousGlyphBounds.y2 = gy2;

    return res;
}

static HRESULT
D3DTR_DrawLCDGlyphViaCache(D3DContext *d3dc, D3DSDOps *dstOps,
                           GlyphInfo *ginfo, jint x, jint y,
                           jint glyphIndex, jint totalGlyphs,
                           jboolean rgbOrder, jint contrast)
{
    HRESULT res;
    D3DGlyphCache *pLCDGCache;
    CacheCellInfo *cell;
    GlyphCacheInfo *gcache;
    jint dx1, dy1, dx2, dy2;
    jfloat dtx1, dty1, dtx2, dty2;

    J2dTraceLn(J2D_TRACE_VERBOSE, "D3DTR_DrawLCDGlyphViaCache");

    // the glyph cache is initialized before this method is called
    pLCDGCache = d3dc->GetLCDGlyphCache();

    if (glyphMode != MODE_USE_CACHE_LCD) {
        D3DTR_DisableGlyphModeState(d3dc);

        res = d3dc->BeginScene(STATE_TEXTUREOP);
        RETURN_STATUS_IF_FAILED(res);

        pLCDGCache->CheckGlyphCacheByteOrder(rgbOrder);

        res = D3DTR_EnableLCDGlyphModeState(d3dc, dstOps, JNI_TRUE, contrast);
        RETURN_STATUS_IF_FAILED(res);

        glyphMode = MODE_USE_CACHE_LCD;
    }

    gcache = pLCDGCache->GetGlyphCache();
    cell = AccelGlyphCache_GetCellInfoForCache(ginfo, gcache);
    if (cell == NULL) {
        // attempt to add glyph to accelerated glyph cache
        res = pLCDGCache->AddGlyph(ginfo);
        RETURN_STATUS_IF_FAILED(res);

        // we'll just no-op in the rare case that the cell is NULL
        cell = AccelGlyphCache_GetCellInfoForCache(ginfo, gcache);
        RETURN_STATUS_IF_NULL(cell, E_FAIL);
    }

    cell->timesRendered++;

    // location of the glyph in the destination's coordinate space
    dx1 = x;
    dy1 = y;
    dx2 = dx1 + ginfo->width;
    dy2 = dy1 + ginfo->height;

    // copy destination into second cached texture, if necessary
    D3DTR_UpdateCachedDestination(d3dc,
                                  dstOps, ginfo,
                                  dx1, dy1,
                                  dx2, dy2,
                                  dx1 + cell->leftOff,  // adjusted dx1
                                  dx2 + cell->rightOff, // adjusted dx2
                                  glyphIndex, totalGlyphs);

    // texture coordinates of the destination tile
    dtx1 = ((jfloat)(dx1 - cachedDestBounds.x1)) / D3DTR_CACHED_DEST_WIDTH;
    dty1 = ((jfloat)(dy1 - cachedDestBounds.y1)) / D3DTR_CACHED_DEST_HEIGHT;
    dtx2 = ((jfloat)(dx2 - cachedDestBounds.x1)) / D3DTR_CACHED_DEST_WIDTH;
    dty2 = ((jfloat)(dy2 - cachedDestBounds.y1)) / D3DTR_CACHED_DEST_HEIGHT;

    // render composed texture to the destination surface
    return d3dc->pVCacher->DrawTexture((jfloat)dx1, (jfloat)dy1,
                                       (jfloat)dx2, (jfloat)dy2,
                                        cell->tx1, cell->ty1,
                                        cell->tx2, cell->ty2,
                                        dtx1, dty1, dtx2, dty2);
}

static HRESULT
D3DTR_DrawGrayscaleGlyphNoCache(D3DContext *d3dc,
                                GlyphInfo *ginfo, jint x, jint y)
{
    jint tw, th;
    jint sx, sy, sw, sh;
    jint x0;
    jint w = ginfo->width;
    jint h = ginfo->height;
    HRESULT res = S_OK;

    J2dTraceLn(J2D_TRACE_VERBOSE, "D3DTR_DrawGrayscaleGlyphNoCache");

    if (glyphMode != MODE_NO_CACHE_GRAY) {
        D3DTR_DisableGlyphModeState(d3dc);

        res = d3dc->BeginScene(STATE_MASKOP);
        RETURN_STATUS_IF_FAILED(res);

        glyphMode = MODE_NO_CACHE_GRAY;
    }

    x0 = x;
    tw = D3D_MASK_CACHE_TILE_WIDTH;
    th = D3D_MASK_CACHE_TILE_HEIGHT;

    for (sy = 0; sy < h; sy += th, y += th) {
        x = x0;
        sh = ((sy + th) > h) ? (h - sy) : th;

        for (sx = 0; sx < w; sx += tw, x += tw) {
            sw = ((sx + tw) > w) ? (w - sx) : tw;

            res = d3dc->GetMaskCache()->AddMaskQuad(sx, sy, x, y, sw, sh,
                                                    w, ginfo->image);
        }
    }

    return res;
}

static HRESULT
D3DTR_DrawLCDGlyphNoCache(D3DContext *d3dc, D3DSDOps *dstOps,
                          GlyphInfo *ginfo, jint x, jint y,
                          jint rowBytesOffset,
                          jboolean rgbOrder, jint contrast)
{
    jfloat tx1, ty1, tx2, ty2;
    jfloat dx1, dy1, dx2, dy2;
    jfloat dtx1, dty1, dtx2, dty2;
    jint tw, th;
    jint sx, sy, sw, sh;
    jint cx1, cy1, cx2, cy2;
    jint x0;
    jint w = ginfo->width;
    jint h = ginfo->height;
    TileFormat tileFormat = rgbOrder ? TILEFMT_3BYTE_RGB : TILEFMT_3BYTE_BGR;

    IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();
    D3DResource *pBlitTextureRes, *pCachedDestTextureRes;
    IDirect3DTexture9 *pBlitTexture;
    IDirect3DSurface9 *pCachedDestSurface, *pDst;
    HRESULT res;

    J2dTraceLn(J2D_TRACE_VERBOSE, "D3DTR_DrawLCDGlyphNoCache");

    RETURN_STATUS_IF_NULL(dstOps->pResource, E_FAIL);
    RETURN_STATUS_IF_NULL(pDst = dstOps->pResource->GetSurface(), E_FAIL);

    res = d3dc->GetResourceManager()->GetBlitTexture(&pBlitTextureRes);
    RETURN_STATUS_IF_FAILED(res);

    res = d3dc->GetResourceManager()->
        GetCachedDestTexture(dstOps->pResource->GetDesc()->Format,
                             &pCachedDestTextureRes);
    RETURN_STATUS_IF_FAILED(res);

    pBlitTexture = pBlitTextureRes->GetTexture();
    pCachedDestSurface = pCachedDestTextureRes->GetSurface();

    if (glyphMode != MODE_NO_CACHE_LCD) {
        D3DTR_DisableGlyphModeState(d3dc);

        res = d3dc->BeginScene(STATE_TEXTUREOP);
        RETURN_STATUS_IF_FAILED(res);
        res = D3DTR_EnableLCDGlyphModeState(d3dc,dstOps, JNI_FALSE, contrast);
        RETURN_STATUS_IF_FAILED(res);

        glyphMode = MODE_NO_CACHE_LCD;
    }

    x0 = x;
    tx1 = 0.0f;
    ty1 = 0.0f;
    dtx1 = 0.0f;
    dty1 = 0.0f;
    tw = D3DTR_NOCACHE_TILE_SIZE;
    th = D3DTR_NOCACHE_TILE_SIZE;

    for (sy = 0; sy < h; sy += th, y += th) {
        x = x0;
        sh = ((sy + th) > h) ? (h - sy) : th;

        for (sx = 0; sx < w; sx += tw, x += tw) {
            sw = ((sx + tw) > w) ? (w - sx) : tw;

            // calculate the bounds of the tile to be copied from the
            // destination into the cached tile
            cx1 = x;
            cy1 = y;
            cx2 = cx1 + sw;
            cy2 = cy1 + sh;

            // need to clamp to the destination bounds, otherwise the
            // StretchRect() call may fail
            if (cx1 < 0)              cx1 = 0;
            if (cy1 < 0)              cy1 = 0;
            if (cx2 > dstOps->width)  cx2 = dstOps->width;
            if (cy2 > dstOps->height) cy2 = dstOps->height;

            if (cx2 > cx1 && cy2 > cy1) {
                // copy LCD mask into glyph texture tile
                d3dc->UploadTileToTexture(pBlitTextureRes,
                                          ginfo->image+rowBytesOffset,
                                          0, 0, sx, sy, sw, sh,
                                          ginfo->rowBytes, tileFormat);

                // update the lower-right glyph texture coordinates
                tx2 = ((jfloat)sw) / D3DC_BLIT_TILE_SIZE;
                ty2 = ((jfloat)sh) / D3DC_BLIT_TILE_SIZE;

                // calculate the actual destination vertices
                dx1 = (jfloat)x;
                dy1 = (jfloat)y;
                dx2 = dx1 + sw;
                dy2 = dy1 + sh;

                // copy destination into cached texture tile (the upper-left
                // corner of the destination region will be positioned at the
                // upper-left corner (0,0) of the texture)
                RECT srcRect = { cx1, cy1, cx2, cy2 };
                RECT dstRect = { cx1-x, cy1-y, cx2-x, cy2-y };
                pd3dDevice->StretchRect(pDst, &srcRect,
                                        pCachedDestSurface,
                                        &dstRect,
                                        D3DTEXF_NONE);

                // update the remaining destination texture coordinates
                dtx2 = ((jfloat)sw) / D3DTR_CACHED_DEST_WIDTH;
                dty2 = ((jfloat)sh) / D3DTR_CACHED_DEST_HEIGHT;

                // render composed texture to the destination surface
                res = d3dc->pVCacher->DrawTexture( dx1,  dy1,  dx2,  dy2,
                                                   tx1,  ty1,  tx2,  ty2,
                                                   dtx1, dty1, dtx2, dty2);

                // unfortunately we need to flush after each tile
                d3dc->FlushVertexQueue();
            }
        }
    }

    return res;
}

// see DrawGlyphList.c for more on this macro...
#define FLOOR_ASSIGN(l, r) \
    if ((r)<0) (l) = ((int)floor(r)); else (l) = ((int)(r))

HRESULT
D3DTR_DrawGlyphList(D3DContext *d3dc, D3DSDOps *dstOps,
                    jint totalGlyphs, jboolean usePositions,
                    jboolean subPixPos, jboolean rgbOrder, jint lcdContrast,
                    jfloat glyphListOrigX, jfloat glyphListOrigY,
                    unsigned char *images, unsigned char *positions)
{
    int glyphCounter;
    HRESULT res = S_OK;
    J2dTraceLn(J2D_TRACE_INFO, "D3DTR_DrawGlyphList");

    RETURN_STATUS_IF_NULL(d3dc, E_FAIL);
    RETURN_STATUS_IF_NULL(d3dc->Get3DDevice(), E_FAIL);
    RETURN_STATUS_IF_NULL(dstOps, E_FAIL);
    RETURN_STATUS_IF_NULL(images, E_FAIL);
    if (usePositions) {
        RETURN_STATUS_IF_NULL(positions, E_FAIL);
    }

    glyphMode = MODE_NOT_INITED;
    isCachedDestValid = JNI_FALSE;

    for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) {
        jint x, y;
        jfloat glyphx, glyphy;
        jboolean grayscale;
        GlyphInfo *ginfo = (GlyphInfo *)jlong_to_ptr(NEXT_LONG(images));

        if (ginfo == NULL) {
            // this shouldn't happen, but if it does we'll just break out...
            J2dRlsTraceLn(J2D_TRACE_ERROR,
                          "D3DTR_DrawGlyphList: glyph info is null");
            break;
        }

        grayscale = (ginfo->rowBytes == ginfo->width);

        if (usePositions) {
            jfloat posx = NEXT_FLOAT(positions);
            jfloat posy = NEXT_FLOAT(positions);
            glyphx = glyphListOrigX + posx + ginfo->topLeftX;
            glyphy = glyphListOrigY + posy + ginfo->topLeftY;
            FLOOR_ASSIGN(x, glyphx);
            FLOOR_ASSIGN(y, glyphy);
        } else {
            glyphx = glyphListOrigX + ginfo->topLeftX;
            glyphy = glyphListOrigY + ginfo->topLeftY;
            FLOOR_ASSIGN(x, glyphx);
            FLOOR_ASSIGN(y, glyphy);
            glyphListOrigX += ginfo->advanceX;
            glyphListOrigY += ginfo->advanceY;
        }

        if (ginfo->image == NULL) {
            continue;
        }

        if (grayscale) {
            // grayscale or monochrome glyph data
            if (ginfo->width <= D3DTR_CACHE_CELL_WIDTH &&
                ginfo->height <= D3DTR_CACHE_CELL_HEIGHT &&
                SUCCEEDED(d3dc->InitGrayscaleGlyphCache()))
            {
                res = D3DTR_DrawGrayscaleGlyphViaCache(d3dc, ginfo, x, y);
            } else {
                res = D3DTR_DrawGrayscaleGlyphNoCache(d3dc, ginfo, x, y);
            }
        } else {
            // LCD-optimized glyph data
            jint rowBytesOffset = 0;

            if (subPixPos) {
                jint frac = (jint)((glyphx - x) * 3);
                if (frac != 0) {
                    rowBytesOffset = 3 - frac;
                    x += 1;
                }
            }

            if (rowBytesOffset == 0 &&
                ginfo->width <= D3DTR_CACHE_CELL_WIDTH &&
                ginfo->height <= D3DTR_CACHE_CELL_HEIGHT &&
                SUCCEEDED(d3dc->InitLCDGlyphCache()))
            {
                res = D3DTR_DrawLCDGlyphViaCache(d3dc, dstOps,
                                                 ginfo, x, y,
                                                 glyphCounter, totalGlyphs,
                                                 rgbOrder, lcdContrast);
            } else {
                res = D3DTR_DrawLCDGlyphNoCache(d3dc, dstOps,
                                                ginfo, x, y,
                                                rowBytesOffset,
                                                rgbOrder, lcdContrast);
            }
        }

        if (FAILED(res)) {
            break;
        }
    }

    D3DTR_DisableGlyphModeState(d3dc);
    return res;
}

JNIEXPORT void JNICALL
Java_sun_java2d_d3d_D3DTextRenderer_drawGlyphList
    (JNIEnv *env, jobject self,
     jint numGlyphs, jboolean usePositions,
     jboolean subPixPos, jboolean rgbOrder, jint lcdContrast,
     jfloat glyphListOrigX, jfloat glyphListOrigY,
     jlongArray imgArray, jfloatArray posArray)
{
    unsigned char *images;

    J2dTraceLn(J2D_TRACE_INFO, "D3DTextRenderer_drawGlyphList");

    images = (unsigned char *)
        env->GetPrimitiveArrayCritical(imgArray, NULL);
    if (images != NULL) {
        D3DContext *d3dc = D3DRQ_GetCurrentContext();
        D3DSDOps *dstOps = D3DRQ_GetCurrentDestination();

        if (usePositions) {
            unsigned char *positions = (unsigned char *)
                env->GetPrimitiveArrayCritical(posArray, NULL);
            if (positions != NULL) {
                D3DTR_DrawGlyphList(d3dc, dstOps,
                                    numGlyphs, usePositions,
                                    subPixPos, rgbOrder, lcdContrast,
                                    glyphListOrigX, glyphListOrigY,
                                    images, positions);
                env->ReleasePrimitiveArrayCritical(posArray,
                                                   positions, JNI_ABORT);
            }
        } else {
            D3DTR_DrawGlyphList(d3dc, dstOps,
                                numGlyphs, usePositions,
                                subPixPos, rgbOrder, lcdContrast,
                                glyphListOrigX, glyphListOrigY,
                                images, NULL);
        }

        // reset current state, and ensure rendering is flushed to dest
        if (d3dc != NULL) {
            d3dc->FlushVertexQueue();
        }

        env->ReleasePrimitiveArrayCritical(imgArray,
                                           images, JNI_ABORT);
    }
}