1 /*
   2  * Copyright (c) 2009, 2015, 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 com.sun.prism.impl;
  27 
  28 import com.sun.prism.Image;
  29 import com.sun.prism.PixelFormat;
  30 import com.sun.prism.ResourceFactory;
  31 import com.sun.prism.ResourceFactoryListener;
  32 import com.sun.prism.Texture;
  33 import com.sun.prism.Texture.Usage;
  34 import com.sun.prism.Texture.WrapMode;
  35 import java.util.Map;
  36 import java.util.WeakHashMap;
  37 import java.util.Collection;
  38 
  39 public abstract class BaseResourceFactory implements ResourceFactory {
  40     private final Map<Image,Texture> clampTexCache;
  41     private final Map<Image,Texture> repeatTexCache;
  42     // Solely used by diffuse and selfillum maps in PhongMaterial for 3D rendering
  43     private final Map<Image,Texture> mipmapTexCache;
  44 
  45     // Use a WeakHashMap as it automatically removes dead objects when they're
  46     // collected
  47     private final WeakHashMap<ResourceFactoryListener,Boolean> listenerMap =
  48             new WeakHashMap<ResourceFactoryListener,Boolean>();
  49 
  50     private Texture regionTexture;
  51     private Texture glyphTexture;
  52     private boolean superShaderAllowed;
  53 
  54     public BaseResourceFactory() {
  55         this(new WeakHashMap<Image,Texture>(),
  56              new WeakHashMap<Image,Texture>(),
  57              new WeakHashMap<Image,Texture>());
  58     }
  59 
  60     public BaseResourceFactory(Map<Image, Texture> clampTexCache,
  61                                Map<Image, Texture> repeatTexCache,
  62                                Map<Image, Texture> mipmapTexCache)
  63     {
  64         this.clampTexCache = clampTexCache;
  65         this.repeatTexCache = repeatTexCache;
  66         this.mipmapTexCache = mipmapTexCache;
  67     }
  68 
  69     @Override public void addFactoryListener(ResourceFactoryListener l) {
  70         listenerMap.put(l, Boolean.TRUE);
  71     }
  72 
  73     @Override public void removeFactoryListener(ResourceFactoryListener l) {
  74         // remove will return null if there is no mapping, so it's safe to call
  75         // with unregistered listeners
  76         listenerMap.remove(l);
  77     }
  78 
  79     @Override public boolean isDeviceReady() {
  80         return true;
  81     }
  82 
  83     protected void clearTextureCache() {
  84         clearTextureCache(clampTexCache);
  85         clearTextureCache(repeatTexCache);
  86         clearTextureCache(mipmapTexCache);
  87     }
  88 
  89     protected void clearTextureCache(Map<Image,Texture> texCache) {
  90         Collection<Texture> texAll = texCache.values();
  91         for (Texture i : texAll) {
  92             i.dispose();
  93         }
  94         texCache.clear();
  95     }
  96 
  97     protected ResourceFactoryListener[] getFactoryListeners() {
  98         return listenerMap.keySet().toArray(new ResourceFactoryListener[0]);
  99     }
 100 
 101     /**
 102      * Called when the factory is reset. Some resources (based in vram) could
 103      * be lost.
 104      */
 105     protected void notifyReset() {
 106         clampTexCache.clear();
 107         repeatTexCache.clear();
 108         mipmapTexCache.clear();
 109 
 110         // Iterate over a *copy* of the key set because listeners may remove
 111         // themselves during the callback
 112         ResourceFactoryListener[] notifyList = getFactoryListeners();
 113         for (ResourceFactoryListener listener : notifyList) {
 114             if (null != listener) {
 115                 listener.factoryReset();
 116             }
 117         }
 118     }
 119 
 120     /**
 121      * Called when the factory's data is released
 122      */
 123     protected void notifyReleased() {
 124         clampTexCache.clear();
 125         repeatTexCache.clear();
 126         mipmapTexCache.clear();
 127 
 128         // Iterate over a *copy* of the key set because listeners may remove
 129         // themselves during the callback
 130         ResourceFactoryListener[] notifyList = getFactoryListeners();
 131         for (ResourceFactoryListener listener : notifyList) {
 132             if (null != listener) {
 133                 listener.factoryReleased();
 134             }
 135         }
 136     }
 137 
 138     static long sizeWithMipMap(int w, int h, PixelFormat format) {
 139         long size = 0;
 140         int bytesPerPixel = format.getBytesPerPixelUnit();
 141         while (w > 1 && h > 1) {
 142             size += ((long) w) * ((long) h);
 143             w = (w + 1) >> 1;
 144             h = (h + 1) >> 1;            
 145         }
 146         size += 1;
 147         return size * bytesPerPixel;
 148     }
 149 
 150     @Override
 151     public Texture getCachedTexture(Image image, WrapMode wrapMode) {
 152        return  getCachedTexture(image, wrapMode, false);
 153     }
 154 
 155     @Override
 156     public Texture getCachedTexture(Image image, WrapMode wrapMode, boolean useMipmap) {
 157         if (image == null) {
 158             throw new IllegalArgumentException("Image must be non-null");
 159         }
 160         Map<Image,Texture> texCache;
 161         if (wrapMode == WrapMode.CLAMP_TO_EDGE) {
 162             // Mipmap not supported with CLAMP mode in current implementation
 163             if (useMipmap) {
 164                 throw new IllegalArgumentException("Mipmap not supported with CLAMP mode: useMipmap = "
 165                         + useMipmap + ", wrapMode = " + wrapMode);
 166             }
 167             texCache = clampTexCache;
 168         } else if (wrapMode == WrapMode.REPEAT) {
 169             texCache = useMipmap ? mipmapTexCache : repeatTexCache;
 170         } else {
 171             throw new IllegalArgumentException("no caching for "+wrapMode);
 172         }
 173          Texture tex = texCache.get(image);
 174          if (tex != null) {
 175              tex.lock();
 176              if (tex.isSurfaceLost()) {
 177                  texCache.remove(image);
 178                  tex = null;
 179              }
 180          }
 181          int serial = image.getSerial();
 182 
 183          // Doesn't apply if useMipmap is true
 184          if (!useMipmap && tex == null) {
 185             // Try to share a converted texture from the other cache
 186             Texture othertex = (wrapMode == WrapMode.REPEAT
 187                    ? clampTexCache
 188                    : repeatTexCache).get(image);
 189             if (othertex != null) {
 190                 othertex.lock();
 191                 if (!othertex.isSurfaceLost()) {
 192                     // This conversion operation will fail if the texture is
 193                     // _SIMULATED
 194                     tex = othertex.getSharedTexture(wrapMode);
 195                     if (tex != null) {
 196                         // Technically, our shared texture will maintain that
 197                         // the contents are useful, but for completeness we
 198                         // will register both references as "useful"
 199                         tex.contentsUseful();
 200                         texCache.put(image, tex);
 201                     }
 202                 }
 203                 othertex.unlock();
 204             }
 205         }
 206 
 207         if (tex == null) {
 208             int w = image.getWidth();
 209             int h = image.getHeight();
 210             TextureResourcePool pool = getTextureResourcePool();
 211             // Mipmap will use more memory
 212             long size = useMipmap ? sizeWithMipMap(w, h, image.getPixelFormat())
 213                     : pool.estimateTextureSize(w, h, image.getPixelFormat());
 214             if (!pool.prepareForAllocation(size)) {
 215                 return null;
 216             }
 217 
 218             tex = createTexture(image, Usage.DEFAULT, wrapMode, useMipmap);
 219             if (tex != null) {
 220                 tex.setLastImageSerial(serial);
 221                 texCache.put(image, tex);
 222             }
 223         } else if (tex.getLastImageSerial() != serial) {
 224             tex.update(image, 0, 0, image.getWidth(), image.getHeight(), false);
 225             tex.setLastImageSerial(serial);
 226         }
 227         return tex;
 228     }
 229 
 230     @Override
 231     public Texture createTexture(Image image, Usage usageHint, WrapMode wrapMode) {
 232         return createTexture(image, usageHint, wrapMode, false);
 233     }
 234 
 235     @Override
 236     public Texture createTexture(Image image, Usage usageHint, WrapMode wrapMode,
 237             boolean useMipmap) {
 238         PixelFormat format = image.getPixelFormat();
 239         int w = image.getWidth();
 240         int h = image.getHeight();
 241 
 242         Texture tex = createTexture(format, usageHint, wrapMode, w, h, useMipmap);
 243         // creation of a texture does not require flushing the vertex buffer
 244         // since there are no pending vertices that depend on this new texture,
 245         // so pass skipFlush=true here...
 246         if (tex != null) {
 247             tex.update(image, 0, 0, w, h, true);
 248             tex.contentsUseful();
 249         }
 250         return tex;
 251     }
 252 
 253     @Override
 254     public Texture createMaskTexture(int width, int height, WrapMode wrapMode) {
 255         return createTexture(PixelFormat.BYTE_ALPHA,
 256                              Usage.DEFAULT, wrapMode,
 257                              width, height);
 258     }
 259 
 260     @Override
 261     public Texture createFloatTexture(int width, int height) {
 262         return createTexture(PixelFormat.FLOAT_XYZW,
 263                              Usage.DEFAULT, WrapMode.CLAMP_TO_ZERO,
 264                              width, height);
 265     }
 266 
 267     @Override
 268     public void setRegionTexture(Texture texture) {
 269         regionTexture = texture;
 270         superShaderAllowed = PrismSettings.superShader &&
 271                              regionTexture != null &&
 272                              glyphTexture != null;
 273     }
 274 
 275     @Override
 276     public Texture getRegionTexture() {
 277         return regionTexture;
 278     }
 279 
 280     @Override
 281     public void setGlyphTexture(Texture texture) {
 282         glyphTexture = texture;
 283         superShaderAllowed = PrismSettings.superShader &&
 284                              regionTexture != null &&
 285                              glyphTexture != null;
 286     }
 287 
 288     @Override
 289     public Texture getGlyphTexture() {
 290         return glyphTexture;
 291     }
 292 
 293     @Override
 294     public boolean isSuperShaderAllowed() {
 295         return superShaderAllowed;
 296     }
 297 
 298     protected boolean canClampToZero() {
 299         return true;
 300     }
 301 
 302     protected boolean canClampToEdge() {
 303         return true;
 304     }
 305 
 306     protected boolean canRepeat() {
 307         return true;
 308     }
 309 
 310     @Override
 311     public boolean isWrapModeSupported(WrapMode mode) {
 312         switch (mode) {
 313             case CLAMP_NOT_NEEDED:
 314                 return true;
 315             case CLAMP_TO_EDGE:
 316                 return canClampToEdge();
 317             case REPEAT:
 318                 return canRepeat();
 319             case CLAMP_TO_ZERO:
 320                 return canClampToZero();
 321             case CLAMP_TO_EDGE_SIMULATED:
 322             case CLAMP_TO_ZERO_SIMULATED:
 323             case REPEAT_SIMULATED:
 324                 throw new InternalError("Cannot test support for simulated wrap modes");
 325             default:
 326                 throw new InternalError("Unrecognized wrap mode: "+mode);
 327         }
 328     }
 329 }