1 /*
   2  * Copyright (c) 2009, 2014, 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.d3d;
  27 
  28 import java.io.BufferedInputStream;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.lang.reflect.Method;
  32 import java.nio.Buffer;
  33 import java.nio.ByteBuffer;
  34 import java.nio.FloatBuffer;
  35 import java.nio.IntBuffer;
  36 import java.security.AccessController;
  37 import java.security.PrivilegedAction;
  38 import java.util.LinkedList;
  39 import java.util.ListIterator;
  40 import java.util.Map;
  41 
  42 import com.sun.glass.ui.Screen;
  43 import com.sun.prism.MediaFrame;
  44 import com.sun.prism.Mesh;
  45 import com.sun.prism.MeshView;
  46 import com.sun.prism.MultiTexture;
  47 import com.sun.prism.PhongMaterial;
  48 import com.sun.prism.PixelFormat;
  49 import com.sun.prism.Presentable;
  50 import com.sun.prism.PresentableState;
  51 import com.sun.prism.Texture;
  52 import com.sun.prism.Texture.Usage;
  53 import com.sun.prism.Texture.WrapMode;
  54 import com.sun.prism.d3d.D3DResource.D3DRecord;
  55 import com.sun.prism.impl.PrismSettings;
  56 import com.sun.prism.impl.VertexBuffer;
  57 import com.sun.prism.impl.ps.BaseShaderFactory;
  58 import com.sun.prism.impl.TextureResourcePool;
  59 import com.sun.prism.ps.Shader;
  60 import com.sun.prism.ps.ShaderFactory;
  61 
  62 class D3DResourceFactory extends BaseShaderFactory {
  63 
  64     private final D3DContext context;
  65     private final int maxTextureSize;
  66 
  67     /**
  68      * List of disposer records for d3d resources created by the pipeline.
  69      * @see D3DResource
  70      */
  71     private final LinkedList<D3DResource.D3DRecord> records =
  72         new LinkedList<D3DResource.D3DRecord>();
  73 
  74     D3DResourceFactory(long pContext, Screen screen) {
  75         context = new D3DContext(pContext, screen, this);
  76         context.initState();
  77         maxTextureSize = computeMaxTextureSize();
  78 
  79         if (PrismSettings.noClampToZero && PrismSettings.verbose) {
  80             System.out.println("prism.noclamptozero not supported by D3D");
  81         }
  82     }
  83 
  84     D3DContext getContext() {
  85         return context;
  86     }
  87 
  88     public TextureResourcePool getTextureResourcePool() {
  89         return D3DVramPool.instance;
  90     }
  91 
  92     static final int STATS_FREQUENCY = PrismSettings.prismStatFrequency;
  93     private int nFrame = -1;
  94     private D3DFrameStats frameStats;
  95 
  96     private void displayPrismStatistics() {
  97         if (STATS_FREQUENCY > 0) {
  98             if (++nFrame == STATS_FREQUENCY) {
  99                 nFrame = 0;
 100                 frameStats = context.getFrameStats(true, frameStats);
 101                 if (frameStats != null) {
 102                     System.err.println(frameStats.toDebugString(STATS_FREQUENCY));
 103                 }
 104             }
 105         }
 106     }
 107 
 108     @Override
 109     public boolean isDeviceReady() {
 110         displayPrismStatistics();
 111         return context.testLostStateAndReset();
 112     }
 113 
 114     static int nextPowerOfTwo(int val, int max) {
 115         if (val > max) {
 116             return 0;
 117         }
 118         int i = 1;
 119         while (i < val) {
 120             i *= 2;
 121         }
 122         return i;
 123     }
 124 
 125     @Override
 126     public boolean isCompatibleTexture(Texture tex) {
 127         return tex instanceof D3DTexture;
 128     }
 129   
 130     @Override
 131     public D3DTexture createTexture(PixelFormat format, Usage usagehint,
 132             WrapMode wrapMode, int w, int h) {
 133         return createTexture(format, usagehint, wrapMode, w, h, false);
 134     }
 135 
 136     @Override
 137     public D3DTexture createTexture(PixelFormat format, Usage usagehint,
 138             WrapMode wrapMode, int w, int h, boolean useMipmap) {
 139         if (!isFormatSupported(format)) {
 140             throw new UnsupportedOperationException(
 141                 "Pixel format " + format +
 142                 " not supported on this device");
 143         }
 144 
 145         if (format == PixelFormat.MULTI_YCbCr_420) {
 146             throw new UnsupportedOperationException("MULTI_YCbCr_420 textures require a MediaFrame");
 147         }
 148 
 149         int allocw, alloch;
 150         if (PrismSettings.forcePow2) {
 151             allocw = nextPowerOfTwo(w, Integer.MAX_VALUE);
 152             alloch = nextPowerOfTwo(h, Integer.MAX_VALUE);
 153         } else {
 154             allocw = w;
 155             alloch = h;
 156         }
 157         D3DVramPool pool = D3DVramPool.instance;
 158         long size = pool.estimateTextureSize(allocw, alloch, format);
 159         if (!pool.prepareForAllocation(size)) {
 160             return null;
 161         }
 162         long pResource = nCreateTexture(context.getContextHandle(),
 163                                         format.ordinal(), usagehint.ordinal(),
 164                                         false /*isRTT*/, allocw, alloch, 0, useMipmap);
 165         if (pResource == 0L) {
 166             return null;
 167         }
 168 
 169         int texw = nGetTextureWidth(pResource);
 170         int texh = nGetTextureHeight(pResource);
 171         if (wrapMode != WrapMode.CLAMP_NOT_NEEDED && (w < texw || h < texh)) {
 172             wrapMode = wrapMode.simulatedVersion();
 173         }
 174         return new D3DTexture(context, format, wrapMode, pResource, texw, texh, w, h, useMipmap);
 175     }
 176 
 177     @Override
 178     public Texture createTexture(MediaFrame frame) {
 179         frame.holdFrame();
 180 
 181         int width = frame.getWidth();
 182         int height = frame.getHeight();
 183         int texWidth = frame.getEncodedWidth();
 184         int texHeight = frame.getEncodedHeight();
 185         PixelFormat texFormat = frame.getPixelFormat();
 186 
 187         if (texFormat == PixelFormat.MULTI_YCbCr_420) {
 188             // Create a MultiTexture instead
 189             MultiTexture tex = new MultiTexture(texFormat, WrapMode.CLAMP_TO_EDGE, width, height);
 190 
 191             // create/add the subtextures
 192             // plane indices: 0 = luma, 1 = Cb, 2 = Cr, 3 (optional) = alpha
 193             for (int index = 0; index < frame.planeCount(); index++) {
 194                 int subWidth = texWidth;
 195                 int subHeight =  texHeight; // might not match height if height is odd
 196 
 197                 if (index == PixelFormat.YCBCR_PLANE_CHROMABLUE
 198                         || index == PixelFormat.YCBCR_PLANE_CHROMARED)
 199                 {
 200                     subWidth /= 2;
 201                     subHeight /= 2;
 202                 }
 203 
 204                 D3DTexture subTex = createTexture(PixelFormat.BYTE_ALPHA, Usage.DYNAMIC, WrapMode.CLAMP_TO_EDGE,
 205                                                   subWidth, subHeight);
 206                 if (subTex == null) {
 207                     tex.dispose();
 208                     return null;
 209                 }
 210                 tex.setTexture(subTex, index);
 211             }
 212 
 213             frame.releaseFrame();
 214             return tex;
 215         } else {
 216             D3DVramPool pool = D3DVramPool.instance;
 217             long size = pool.estimateTextureSize(texWidth, texHeight, texFormat);
 218             if (!pool.prepareForAllocation(size)) {
 219                 return null;
 220             }
 221             long pResource = nCreateTexture(context.getContextHandle(),
 222                     texFormat.ordinal(), Usage.DYNAMIC.ordinal(),
 223                     false, texWidth, texHeight, 0, false);
 224             if (0 == pResource) {
 225                 return null;
 226             }
 227 
 228             int physWidth = nGetTextureWidth(pResource);
 229             int physHeight = nGetTextureHeight(pResource);
 230             WrapMode wrapMode = (texWidth < physWidth || texHeight < physHeight)
 231                     ? WrapMode.CLAMP_TO_EDGE_SIMULATED : WrapMode.CLAMP_TO_EDGE;
 232             D3DTexture tex = new D3DTexture(context, texFormat, wrapMode, pResource,
 233                                             physWidth, physHeight, width, height, false);
 234             frame.releaseFrame();
 235             return tex;
 236         }
 237     }
 238 
 239     public int getRTTWidth(int w, WrapMode wrapMode) {
 240         // D3DRTTexture returns the requested dimension as the content dimension
 241         // so the answer here is just "w" despite the fact that a pow2 adjustment
 242         // is made for the actual allocation.  Typically, D3D supports non-pow2
 243         // textures on every implementation so the pow2 code below is not really
 244         // encountered in practice anyway (it's only supported for "debugging").
 245 //        if (PrismSettings.forcePow2) {
 246 //            w = nextPowerOfTwo(w, Integer.MAX_VALUE);
 247 //        }
 248         return w;
 249     }
 250 
 251     public int getRTTHeight(int h, WrapMode wrapMode) {
 252         // D3DRTTexture returns the requested dimension as the content dimension
 253         // so the answer here is just "h" despite the fact that a pow2 adjustment
 254         // is made for the actual allocation.  Typically, D3D supports non-pow2
 255         // textures on every implementation so the pow2 code below is not really
 256         // encountered in practice anyway (it's only supported for "debugging").
 257 //        if (PrismSettings.forcePow2) {
 258 //            h = nextPowerOfTwo(h, Integer.MAX_VALUE);
 259 //        }
 260         return h;
 261     }
 262 
 263     @Override
 264     public D3DRTTexture createRTTexture(int width, int height, WrapMode wrapMode) {
 265         return createRTTexture(width, height, wrapMode, false);
 266     }
 267 
 268     @Override
 269     public D3DRTTexture createRTTexture(int width, int height, WrapMode wrapMode, boolean msaa) {
 270         if (PrismSettings.verbose && context.isLost()) {
 271             System.err.println("RT Texture allocation while the device is lost");
 272         }
 273 
 274         int createw = width;
 275         int createh = height;
 276         int cx = 0;
 277         int cy = 0;
 278         if (PrismSettings.forcePow2) {
 279             createw = nextPowerOfTwo(createw, Integer.MAX_VALUE);
 280             createh = nextPowerOfTwo(createh, Integer.MAX_VALUE);
 281         }
 282         D3DVramPool pool = D3DVramPool.instance;
 283         int aaSamples;
 284         if (msaa) {
 285             int maxSamples = D3DPipeline.getInstance().getMaxSamples();
 286             aaSamples =  maxSamples < 2 ? 0 : (maxSamples < 4 ? 2 : 4);
 287         } else {
 288             aaSamples = 0;
 289         }
 290         // TODO: 3D - Improve estimate to include if multisample rtt
 291         long size = pool.estimateRTTextureSize(width, height, false);
 292         if (!pool.prepareForAllocation(size)) {
 293             return null;
 294         }
 295 
 296         long pResource = nCreateTexture(context.getContextHandle(),
 297                                         PixelFormat.INT_ARGB_PRE.ordinal(),
 298                                         Usage.DEFAULT.ordinal(),
 299                                         true /*isRTT*/, createw, createh, aaSamples, false);
 300         if (pResource == 0L) {
 301             return null;
 302         }
 303 
 304         int texw = nGetTextureWidth(pResource);
 305         int texh = nGetTextureHeight(pResource);
 306         D3DRTTexture rtt = new D3DRTTexture(context, wrapMode, pResource, texw, texh,
 307                                             cx, cy, width, height, aaSamples);
 308         // ensure the RTTexture is cleared to all zeros before returning
 309         // (Decora relies on the Java2D behavior, where an image is expected
 310         // to be fully transparent after initialization)
 311         rtt.createGraphics().clear();
 312         return rtt;
 313     }
 314 
 315     public Presentable createPresentable(PresentableState pState) {
 316         if (PrismSettings.verbose && context.isLost()) {
 317             System.err.println("SwapChain allocation while the device is lost");
 318         }
 319 
 320         long pResource = nCreateSwapChain(context.getContextHandle(),
 321                                           pState.getNativeView(),
 322                                           PrismSettings.isVsyncEnabled);
 323 
 324         if (pResource != 0L) {
 325             int width = D3DResourceFactory.nGetTextureWidth(pResource);
 326             int height = D3DResourceFactory.nGetTextureHeight(pResource);
 327             D3DRTTexture rtt = createRTTexture(width, height, WrapMode.CLAMP_NOT_NEEDED, pState.isMSAA());
 328             if (PrismSettings.dirtyOptsEnabled) {
 329                 rtt.contentsUseful();
 330             }
 331 
 332             if (rtt != null) {
 333                 return new D3DSwapChain(context, pResource, rtt);
 334             }
 335 
 336             D3DResourceFactory.nReleaseResource(context.getContextHandle(), pResource);
 337         }
 338         return null;
 339 
 340     }
 341 
 342 
 343     public VertexBuffer createVertexBuffer(int maxQuads) {
 344         return new VertexBuffer(maxQuads);
 345     }
 346 
 347     private static ByteBuffer getBuffer(InputStream is) {
 348         if (is == null) {
 349            throw new RuntimeException("InputStream must be non-null");
 350         }
 351         try {
 352             int len = 4096;
 353             byte[] data = new byte[len];
 354             BufferedInputStream bis = new BufferedInputStream(is, len);
 355             int offset = 0;
 356             int readBytes = -1;
 357             while ((readBytes = bis.read(data, offset, len - offset)) != -1) {
 358                 offset += readBytes;
 359                 if (len - offset == 0) {
 360                     // grow the array
 361                     len *= 2;
 362                     // was
 363                     // data = Arrays.copyOf(data, len);
 364                     //
 365                     byte[] newdata = new byte[len];
 366                     System.arraycopy(data, 0, newdata, 0, data.length);
 367                     data = newdata;
 368                 }
 369             }
 370             bis.close();
 371             // NOTE: for now the D3DShader native code only knows how to
 372             // deal with direct ByteBuffers, so we have to dump the byte[]
 373             // into a newly allocated direct buffer...
 374             ByteBuffer buf = ByteBuffer.allocateDirect(offset);
 375             buf.put(data, 0, offset);
 376             return buf;
 377         } catch (IOException e) {
 378             throw new RuntimeException("Error loading D3D shader object", e);
 379         }
 380     }
 381 
 382     public Shader createShader(InputStream pixelShaderCode,
 383                                Map<String, Integer> samplers,
 384                                Map<String, Integer> params,
 385                                int maxTexCoordIndex,
 386                                boolean isPixcoordUsed,
 387                                boolean isPerVertexColorUsed)
 388     {
 389         long shaderHandle = D3DShader.init(
 390                 context.getContextHandle(), getBuffer(pixelShaderCode),
 391                 maxTexCoordIndex, isPixcoordUsed, isPerVertexColorUsed);
 392 
 393         return new D3DShader(context, shaderHandle, params);
 394     }
 395 
 396     public Shader createStockShader(final String name) {
 397         if (name == null) {
 398             throw new IllegalArgumentException("Shader name must be non-null");
 399         }
 400         try {
 401             InputStream stream = AccessController.doPrivileged(
 402                     (PrivilegedAction<InputStream>) () -> D3DResourceFactory.class.
 403                            getResourceAsStream("hlsl/" + name + ".obj")
 404             );
 405             Class klass = Class.forName("com.sun.prism.shader." + name + "_Loader");
 406             Method m = klass.getMethod("loadShader",
 407                 new Class[] { ShaderFactory.class, InputStream.class });
 408             return (Shader)m.invoke(null, new Object[] { this, stream });
 409         } catch (Throwable e) {
 410             e.printStackTrace();
 411             throw new InternalError("Error loading stock shader " + name);
 412         }
 413     }
 414 
 415     @Override
 416     public void dispose() {
 417         throw new UnsupportedOperationException("Not supported yet.");
 418     }
 419 
 420     @Override
 421     public boolean isFormatSupported(PixelFormat format) {
 422         return true;
 423     }
 424 
 425     private int computeMaxTextureSize() {
 426         int size = nGetMaximumTextureSize(context.getContextHandle());
 427         if (PrismSettings.verbose) {
 428             System.err.println("Maximum supported texture size: " + size);
 429         }
 430         if (size > PrismSettings.maxTextureSize) {
 431             size = PrismSettings.maxTextureSize;
 432             if (PrismSettings.verbose) {
 433                 System.err.println("Maximum texture size clamped to " + size);
 434             }
 435         }
 436         return size;
 437     }
 438 
 439     public int getMaximumTextureSize() {
 440         return maxTextureSize;
 441     }
 442 
 443     @Override
 444     protected void notifyReset() {
 445         for (ListIterator<D3DRecord> it = records.listIterator(); it.hasNext();) {
 446             D3DRecord r = it.next();
 447             if (r.isDefaultPool()) {
 448                 r.markDisposed();
 449                 it.remove();
 450             }
 451         }
 452         super.notifyReset();
 453     }
 454 
 455     @Override
 456     protected void notifyReleased() {
 457         for (ListIterator<D3DRecord> it = records.listIterator(); it.hasNext();) {
 458             D3DRecord r = it.next();
 459             r.markDisposed();
 460         }
 461         records.clear();
 462         super.notifyReleased();
 463     }
 464 
 465     void addRecord(D3DRecord record) {
 466         records.add(record);
 467     }
 468 
 469     void removeRecord(D3DRecord record) {
 470         records.remove(record);
 471     }
 472 
 473     public PhongMaterial createPhongMaterial() {
 474         return D3DPhongMaterial.create(context);
 475     }
 476 
 477     public MeshView createMeshView(Mesh mesh) {
 478         return D3DMeshView.create(context, (D3DMesh) mesh);
 479 
 480     }
 481 
 482     public Mesh createMesh() {
 483         return D3DMesh.create(context);
 484     }
 485 
 486     static native long nGetContext(int adapterOrdinal);
 487     static native boolean nIsDefaultPool(long pResource);
 488     static native int nTestCooperativeLevel(long pContext);
 489     static native int nResetDevice(long pContext);
 490     static native long nCreateTexture(long pContext,
 491                                       int format, int hint,
 492                                       boolean isRTT,
 493                                       int width, int height, int samples,
 494                                       boolean useMipmap);
 495     static native long nCreateSwapChain(long pContext, long hwnd,
 496                                         boolean isVsyncEnabled);
 497     static native int nReleaseResource(long pContext, long resource);
 498     static native int nGetMaximumTextureSize(long pContext);
 499     static native int nGetTextureWidth(long pResource);
 500     static native int nGetTextureHeight(long pResource);
 501     static native int nReadPixelsI(long pContext, long pResource,
 502                                     long length,
 503                                     Buffer pixels, int[] arr,
 504                                     int contentWidth, int contentHeight);
 505     static native int nReadPixelsB(long pContext, long pResource,
 506                                     long length,
 507                                     Buffer pixels, byte[] arr,
 508                                     int contentWidth, int contentHeight);
 509     static native int nUpdateTextureI(long contextHandle, long pResource,
 510                                       IntBuffer buf, int[] pixels,
 511                                       int dstx, int dsty,
 512                                       int srcx, int srcy,
 513                                       int srcw, int srch, int srcscan);
 514     static native int nUpdateTextureF(long contextHandle, long pResource,
 515                                       FloatBuffer buf, float[] pixels,
 516                                       int dstx, int dsty,
 517                                       int srcx, int srcy,
 518                                       int srcw, int srch, int srcscan);
 519     static native int nUpdateTextureB(long contextHandle, long pResource,
 520                                       ByteBuffer buf, byte[] pixels,
 521                                       int formatHint,
 522                                       int dstx, int dsty,
 523                                       int srcx, int srcy,
 524                                       int srcw, int srch, int srcscan);
 525 
 526     static native long nGetDevice(long pContext);
 527     static native long nGetNativeTextureObject(long pResource);
 528 }