1 /* 2 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.font; 27 28 import java.io.*; 29 import java.util.*; 30 31 import sun.awt.*; 32 import sun.java2d.xr.*; 33 34 /** 35 * Glyph cache used by the XRender pipeline. 36 * 37 * @author Clemens Eisserer 38 */ 39 40 public class XRGlyphCache implements GlyphDisposedListener { 41 XRBackend con; 42 XRCompositeManager maskBuffer; 43 HashMap<MutableInteger, XRGlyphCacheEntry> cacheMap = new HashMap<MutableInteger, XRGlyphCacheEntry>(256); 44 45 int nextID = 1; 46 MutableInteger tmp = new MutableInteger(0); 47 48 int grayGlyphSet; 49 int lcdGlyphSet; 50 51 int time = 0; 52 int cachedPixels = 0; 53 static final int MAX_CACHED_PIXELS = 100000; 54 55 ArrayList<Integer> freeGlyphIDs = new ArrayList<Integer>(255); 56 57 static final boolean batchGlyphUpload = true; // Boolean.parseBoolean(System.getProperty("sun.java2d.xrender.batchGlyphUpload")); 58 59 public XRGlyphCache(XRCompositeManager maskBuf) { 60 this.con = maskBuf.getBackend(); 61 this.maskBuffer = maskBuf; 62 63 grayGlyphSet = con.XRenderCreateGlyphSet(XRUtils.PictStandardA8); 64 lcdGlyphSet = con.XRenderCreateGlyphSet(XRUtils.PictStandardARGB32); 65 66 StrikeCache.addGlyphDisposedListener(this); 67 } 68 69 public void glyphDisposed(ArrayList<Long> glyphPtrList) { 70 try { 71 SunToolkit.awtLock(); 72 73 GrowableIntArray glyphIDList = new GrowableIntArray(1, glyphPtrList.size()); 74 for (long glyphPtr : glyphPtrList) { 75 int glyphID = XRGlyphCacheEntry.getGlyphID(glyphPtr); 76 77 //Check if glyph hasn't been freed already 78 if (glyphID != 0) { 79 glyphIDList.addInt(glyphID); 80 } 81 } 82 freeGlyphs(glyphIDList); 83 } finally { 84 SunToolkit.awtUnlock(); 85 } 86 } 87 88 protected int getFreeGlyphID() { 89 if (freeGlyphIDs.size() > 0) { 90 int newID = freeGlyphIDs.remove(freeGlyphIDs.size() - 1); 91 return newID; 92 } 93 return nextID++; 94 } 95 96 protected XRGlyphCacheEntry getEntryForPointer(long imgPtr) { 97 int id = XRGlyphCacheEntry.getGlyphID(imgPtr); 98 99 if (id == 0) { 100 return null; 101 } 102 103 tmp.setValue(id); 104 return cacheMap.get(tmp); 105 } 106 107 public XRGlyphCacheEntry[] cacheGlyphs(GlyphList glyphList) { 108 time++; 109 110 XRGlyphCacheEntry[] entries = new XRGlyphCacheEntry[glyphList.getNumGlyphs()]; 111 long[] imgPtrs = glyphList.getImages(); 112 ArrayList<XRGlyphCacheEntry> uncachedGlyphs = null; 113 114 for (int i = 0; i < glyphList.getNumGlyphs(); i++) { 115 XRGlyphCacheEntry glyph; 116 117 // Find uncached glyphs and queue them for upload 118 if ((glyph = getEntryForPointer(imgPtrs[i])) == null) { 119 glyph = new XRGlyphCacheEntry(imgPtrs[i], glyphList); 120 glyph.setGlyphID(getFreeGlyphID()); 121 cacheMap.put(new MutableInteger(glyph.getGlyphID()), glyph); 122 123 if (uncachedGlyphs == null) { 124 uncachedGlyphs = new ArrayList<XRGlyphCacheEntry>(); 125 } 126 uncachedGlyphs.add(glyph); 127 } 128 glyph.setLastUsed(time); 129 entries[i] = glyph; 130 } 131 132 // Add glyphs to cache 133 if (uncachedGlyphs != null) { 134 uploadGlyphs(entries, uncachedGlyphs, glyphList, null); 135 } 136 137 return entries; 138 } 139 140 protected void uploadGlyphs(XRGlyphCacheEntry[] glyphs, ArrayList<XRGlyphCacheEntry> uncachedGlyphs, GlyphList gl, int[] glIndices) { 141 for (XRGlyphCacheEntry glyph : uncachedGlyphs) { 142 cachedPixels += glyph.getPixelCnt(); 143 } 144 145 if (cachedPixels > MAX_CACHED_PIXELS) { 146 clearCache(glyphs); 147 } 148 149 boolean containsLCDGlyphs = containsLCDGlyphs(uncachedGlyphs); 150 List<XRGlyphCacheEntry>[] seperatedGlyphList = seperateGlyphTypes(uncachedGlyphs, containsLCDGlyphs); 151 List<XRGlyphCacheEntry> grayGlyphList = seperatedGlyphList[0]; 152 List<XRGlyphCacheEntry> lcdGlyphList = seperatedGlyphList[1]; 153 154 /* 155 * Some XServers crash when uploading multiple glyphs at once. TODO: 156 * Implement build-switch in local case for distributors who know their 157 * XServer is fixed 158 */ 159 if (batchGlyphUpload) { 160 if (grayGlyphList != null && grayGlyphList.size() > 0) { 161 con.XRenderAddGlyphs(grayGlyphSet, gl, grayGlyphList, generateGlyphImageStream(grayGlyphList)); 162 } 163 if (lcdGlyphList != null && lcdGlyphList.size() > 0) { 164 con.XRenderAddGlyphs(lcdGlyphSet, gl, lcdGlyphList, generateGlyphImageStream(lcdGlyphList)); 165 } 166 } else { 167 ArrayList<XRGlyphCacheEntry> tmpList = new ArrayList<XRGlyphCacheEntry>(1); 168 tmpList.add(null); 169 170 for (XRGlyphCacheEntry entry : uncachedGlyphs) { 171 tmpList.set(0, entry); 172 173 if (entry.getGlyphSet() == grayGlyphSet) { 174 con.XRenderAddGlyphs(grayGlyphSet, gl, tmpList, generateGlyphImageStream(tmpList)); 175 } else { 176 con.XRenderAddGlyphs(lcdGlyphSet, gl, tmpList, generateGlyphImageStream(tmpList)); 177 } 178 } 179 } 180 } 181 182 /** 183 * Seperates lcd and grayscale glyphs queued for upload, and sets the 184 * appropriate glyphset for the cache entries. 185 */ 186 protected List<XRGlyphCacheEntry>[] seperateGlyphTypes(List<XRGlyphCacheEntry> glyphList, boolean containsLCDGlyphs) { 187 ArrayList<XRGlyphCacheEntry> lcdGlyphs = null; 188 ArrayList<XRGlyphCacheEntry> grayGlyphs = null; 189 190 for (XRGlyphCacheEntry cacheEntry : glyphList) { 191 if (cacheEntry.isGrayscale(containsLCDGlyphs)) { 192 if (grayGlyphs == null) { 193 grayGlyphs = new ArrayList<XRGlyphCacheEntry>(glyphList.size()); 194 } 195 cacheEntry.setGlyphSet(grayGlyphSet); 196 grayGlyphs.add(cacheEntry); 197 } else { 198 if (lcdGlyphs == null) { 199 lcdGlyphs = new ArrayList<XRGlyphCacheEntry>(glyphList.size()); 200 } 201 cacheEntry.setGlyphSet(lcdGlyphSet); 202 lcdGlyphs.add(cacheEntry); 203 } 204 } 205 206 return new List[] { grayGlyphs, lcdGlyphs }; 207 } 208 209 /** 210 * Copies the glyph-images into a continous buffer, required for uploading. 211 */ 212 protected byte[] generateGlyphImageStream(List<XRGlyphCacheEntry> glyphList) { 213 boolean isLCDGlyph = glyphList.get(0).getGlyphSet() == lcdGlyphSet; 214 215 ByteArrayOutputStream stream = new ByteArrayOutputStream((isLCDGlyph ? 4 : 1) * 48 * glyphList.size()); 216 for (XRGlyphCacheEntry cacheEntry : glyphList) { 217 cacheEntry.writePixelData(stream, isLCDGlyph); 218 } 219 220 return stream.toByteArray(); 221 } 222 223 protected boolean containsLCDGlyphs(List<XRGlyphCacheEntry> entries) { 224 boolean containsLCDGlyphs = false; 225 226 for (XRGlyphCacheEntry entry : entries) { 227 containsLCDGlyphs = !(entry.getSourceRowBytes() == entry.getWidth()); 228 229 if (containsLCDGlyphs) { 230 return true; 231 } 232 } 233 return false; 234 } 235 236 protected void clearCache(XRGlyphCacheEntry[] glyps) { 237 /* 238 * Glyph uploading is so slow anyway, we can afford some inefficiency 239 * here, as the cache should usually be quite small. TODO: Implement 240 * something not that stupid ;) 241 */ 242 ArrayList<XRGlyphCacheEntry> cacheList = new ArrayList<XRGlyphCacheEntry>(cacheMap.values()); 243 Collections.sort(cacheList, new Comparator<XRGlyphCacheEntry>() { 244 public int compare(XRGlyphCacheEntry e1, XRGlyphCacheEntry e2) { 245 return e2.getLastUsed() - e1.getLastUsed(); 246 } 247 }); 248 249 for (XRGlyphCacheEntry glyph : glyps) { 250 glyph.setPinned(); 251 } 252 253 GrowableIntArray deleteGlyphList = new GrowableIntArray(1, 10); 254 int pixelsToRelease = cachedPixels - MAX_CACHED_PIXELS; 255 256 for (int i = cacheList.size() - 1; i >= 0 && pixelsToRelease > 0; i--) { 257 XRGlyphCacheEntry entry = cacheList.get(i); 258 259 if (!entry.isPinned()) { 260 pixelsToRelease -= entry.getPixelCnt(); 261 deleteGlyphList.addInt(entry.getGlyphID()); 262 } 263 } 264 265 for (XRGlyphCacheEntry glyph : glyps) { 266 glyph.setUnpinned(); 267 } 268 269 freeGlyphs(deleteGlyphList); 270 } 271 272 private void freeGlyphs(GrowableIntArray glyphIdList) { 273 GrowableIntArray removedLCDGlyphs = new GrowableIntArray(1, 10); 274 GrowableIntArray removedGrayscaleGlyphs = new GrowableIntArray(1, 10); 275 276 for (int i=0; i < glyphIdList.getSize(); i++) { 277 int glyphId = glyphIdList.getInt(i); 278 freeGlyphIDs.add(glyphId); 279 280 tmp.setValue(glyphId); 281 XRGlyphCacheEntry entry = cacheMap.get(tmp); 282 cachedPixels -= entry.getPixelCnt(); 283 cacheMap.remove(tmp); 284 285 if (entry.getGlyphSet() == grayGlyphSet) { 286 removedGrayscaleGlyphs.addInt(glyphId); 287 } else { 288 removedLCDGlyphs.addInt(glyphId); 289 } 290 291 entry.setGlyphID(0); 292 } 293 294 if (removedGrayscaleGlyphs.getSize() > 0) { 295 con.XRenderFreeGlyphs(grayGlyphSet, removedGrayscaleGlyphs.getSizedArray()); 296 } 297 298 if (removedLCDGlyphs.getSize() > 0) { 299 con.XRenderFreeGlyphs(lcdGlyphSet, removedLCDGlyphs.getSizedArray()); 300 } 301 } 302 }