1 /*
2  * Copyright (c) 2019, 2021, 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.java2d.metal;
27 
28 import sun.awt.SunHints;
29 import sun.awt.image.PixelConverter;
30 import sun.java2d.SunGraphics2D;
31 import sun.java2d.SurfaceData;
32 import sun.java2d.SurfaceDataProxy;
33 import sun.java2d.loops.CompositeType;
34 import sun.java2d.loops.GraphicsPrimitive;
35 import sun.java2d.loops.MaskFill;
36 import sun.java2d.loops.SurfaceType;
37 import sun.java2d.pipe.ParallelogramPipe;
38 import sun.java2d.pipe.PixelToParallelogramConverter;
39 import sun.java2d.pipe.RenderBuffer;
40 import sun.java2d.pipe.TextPipe;
41 import sun.java2d.pipe.hw.AccelSurface;
42 
43 import java.awt.AlphaComposite;
44 import java.awt.Composite;
45 import java.awt.GraphicsConfiguration;
46 import java.awt.GraphicsEnvironment;
47 import java.awt.Image;
48 import java.awt.Rectangle;
49 import java.awt.Transparency;
50 
51 import java.awt.image.ColorModel;
52 import java.awt.image.Raster;
53 
54 import static sun.java2d.pipe.BufferedOpCodes.DISPOSE_SURFACE;
55 import static sun.java2d.pipe.BufferedOpCodes.FLUSH_SURFACE;
56 import static sun.java2d.pipe.hw.ContextCapabilities.CAPS_MULTITEXTURE;
57 import static sun.java2d.pipe.hw.ContextCapabilities.CAPS_PS30;
58 
59 
60 public abstract class MTLSurfaceData extends SurfaceData
61         implements AccelSurface {
62 
63     /**
64      * Pixel formats
65      */
66     public static final int PF_INT_ARGB        = 0;
67     public static final int PF_INT_ARGB_PRE    = 1;
68     public static final int PF_INT_RGB         = 2;
69     public static final int PF_INT_RGBX        = 3;
70     public static final int PF_INT_BGR         = 4;
71     public static final int PF_INT_BGRX        = 5;
72     public static final int PF_USHORT_565_RGB  = 6;
73     public static final int PF_USHORT_555_RGB  = 7;
74     public static final int PF_USHORT_555_RGBX = 8;
75     public static final int PF_BYTE_GRAY       = 9;
76     public static final int PF_USHORT_GRAY     = 10;
77     public static final int PF_3BYTE_BGR       = 11;
78     /**
79      * SurfaceTypes
80      */
81 
82     private static final String DESC_MTL_SURFACE = "MTL Surface";
83     private static final String DESC_MTL_SURFACE_RTT =
84             "MTL Surface (render-to-texture)";
85     private static final String DESC_MTL_TEXTURE = "MTL Texture";
86 
87 
88     static final SurfaceType MTLSurface =
89             SurfaceType.Any.deriveSubType(DESC_MTL_SURFACE,
90                     PixelConverter.ArgbPre.instance);
91     static final SurfaceType MTLSurfaceRTT =
92             MTLSurface.deriveSubType(DESC_MTL_SURFACE_RTT);
93     static final SurfaceType MTLTexture =
94             SurfaceType.Any.deriveSubType(DESC_MTL_TEXTURE);
95 
96     protected static MTLRenderer mtlRenderPipe;
97     protected static PixelToParallelogramConverter mtlTxRenderPipe;
98     protected static ParallelogramPipe mtlAAPgramPipe;
99     protected static MTLTextRenderer mtlTextPipe;
100     protected static MTLDrawImage mtlImagePipe;
101 
102     static {
103         if (!GraphicsEnvironment.isHeadless()) {
104             MTLRenderQueue rq = MTLRenderQueue.getInstance();
105             mtlImagePipe = new MTLDrawImage();
106             mtlTextPipe = new MTLTextRenderer(rq);
107             mtlRenderPipe = new MTLRenderer(rq);
108             if (GraphicsPrimitive.tracingEnabled()) {
109                 mtlTextPipe = mtlTextPipe.traceWrap();
110                 //The wrapped mtlRenderPipe will wrap the AA pipe as well...
111                 //mtlAAPgramPipe = mtlRenderPipe.traceWrap();
112             }
113             mtlAAPgramPipe = mtlRenderPipe.getAAParallelogramPipe();
114             mtlTxRenderPipe =
115                     new PixelToParallelogramConverter(mtlRenderPipe,
116                             mtlRenderPipe,
117                             1.0, 0.25, true);
118 
MTLBlitLoops.register()119             MTLBlitLoops.register();
MTLMaskFill.register()120             MTLMaskFill.register();
MTLMaskBlit.register()121             MTLMaskBlit.register();
122         }
123     }
124 
125     protected final int scale;
126     protected final int width;
127     protected final int height;
128     protected int type;
129     private MTLGraphicsConfig graphicsConfig;
130     // these fields are set from the native code when the surface is
131     // initialized
132     private int nativeWidth;
133     private int nativeHeight;
134 
135     /**
136      * Returns the appropriate SurfaceType corresponding to the given Metal
137      * surface type constant (e.g. TEXTURE -> MTLTexture).
138      */
getCustomSurfaceType(int mtlType)139     private static SurfaceType getCustomSurfaceType(int mtlType) {
140         switch (mtlType) {
141             case TEXTURE:
142                 return MTLTexture;
143             case RT_TEXTURE:
144                 return MTLSurfaceRTT;
145             default:
146                 return MTLSurface;
147         }
148     }
149 
initOps(MTLGraphicsConfig gc, long pConfigInfo, long pPeerData, long layerPtr, int xoff, int yoff, boolean isOpaque)150     private native void initOps(MTLGraphicsConfig gc, long pConfigInfo, long pPeerData, long layerPtr,
151                                 int xoff, int yoff, boolean isOpaque);
152 
MTLSurfaceData(MTLLayer layer, MTLGraphicsConfig gc, ColorModel cm, int type, int width, int height)153     private MTLSurfaceData(MTLLayer layer, MTLGraphicsConfig gc,
154                            ColorModel cm, int type, int width, int height)
155     {
156         super(getCustomSurfaceType(type), cm);
157         this.graphicsConfig = gc;
158         this.type = type;
159         setBlitProxyKey(gc.getProxyKey());
160 
161         // TEXTURE shouldn't be scaled, it is used for managed BufferedImages.
162         scale = type == TEXTURE ? 1 : gc.getDevice().getScaleFactor();
163         this.width = width * scale;
164         this.height = height * scale;
165 
166         long pConfigInfo = gc.getNativeConfigInfo();
167         long layerPtr = 0L;
168         boolean isOpaque = true;
169         if (layer != null) {
170             layerPtr = layer.getPointer();
171             isOpaque = layer.isOpaque();
172         }
173         initOps(gc, pConfigInfo, 0, layerPtr, 0, 0, isOpaque);
174     }
175 
176     @Override
getDeviceConfiguration()177     public GraphicsConfiguration getDeviceConfiguration() {
178         return graphicsConfig;
179     }
180 
181     /**
182      * Creates a SurfaceData object representing the intermediate buffer
183      * between the Java2D flusher thread and the AppKit thread.
184      */
createData(MTLLayer layer)185     public static MTLLayerSurfaceData createData(MTLLayer layer) {
186         MTLGraphicsConfig gc = (MTLGraphicsConfig)layer.getGraphicsConfiguration();
187         Rectangle r = layer.getBounds();
188         return new MTLLayerSurfaceData(layer, gc, r.width, r.height);
189     }
190 
191     /**
192      * Creates a SurfaceData object representing an off-screen buffer
193      */
createData(MTLGraphicsConfig gc, int width, int height, ColorModel cm, Image image, int type)194     public static MTLOffScreenSurfaceData createData(MTLGraphicsConfig gc,
195                                                      int width, int height,
196                                                      ColorModel cm, Image image,
197                                                      int type) {
198         return new MTLOffScreenSurfaceData(gc, width, height, image, cm,
199                 type);
200     }
201 
202     @Override
getDefaultScaleX()203     public double getDefaultScaleX() {
204         return scale;
205     }
206 
207     @Override
getDefaultScaleY()208     public double getDefaultScaleY() {
209         return scale;
210     }
211 
212     @Override
getBounds()213     public Rectangle getBounds() {
214         return new Rectangle(width, height);
215     }
216 
clearWindow()217     protected native void clearWindow();
218 
initTexture(long pData, boolean isOpaque, int width, int height)219     protected native boolean initTexture(long pData, boolean isOpaque, int width, int height);
220 
initRTexture(long pData, boolean isOpaque, int width, int height)221     protected native boolean initRTexture(long pData, boolean isOpaque, int width, int height);
222 
initFlipBackbuffer(long pData)223     protected native boolean initFlipBackbuffer(long pData);
224 
225     @Override
makeProxyFor(SurfaceData srcData)226     public SurfaceDataProxy makeProxyFor(SurfaceData srcData) {
227         return MTLSurfaceDataProxy.createProxy(srcData, graphicsConfig);
228     }
229 
230     /**
231      * Note: This should only be called from the QFT under the AWT lock.
232      * This method is kept separate from the initSurface() method below just
233      * to keep the code a bit cleaner.
234      */
initSurfaceNow(int width, int height)235     private void initSurfaceNow(int width, int height) {
236         boolean isOpaque = (getTransparency() == Transparency.OPAQUE);
237         boolean success = false;
238 
239         switch (type) {
240             case TEXTURE:
241                 success = initTexture(getNativeOps(), isOpaque, width, height);
242                 break;
243 
244             case RT_TEXTURE:
245                 success = initRTexture(getNativeOps(), isOpaque, width, height);
246                 break;
247 
248             case FLIP_BACKBUFFER:
249                 success = initFlipBackbuffer(getNativeOps());
250                 break;
251 
252             default:
253                 break;
254         }
255 
256         if (!success) {
257             throw new OutOfMemoryError("can't create offscreen surface");
258         }
259     }
260 
261     /**
262      * Initializes the appropriate Metal offscreen surface based on the value
263      * of the type parameter.  If the surface creation fails for any reason,
264      * an OutOfMemoryError will be thrown.
265      */
initSurface(final int width, final int height)266     protected void initSurface(final int width, final int height) {
267         MTLRenderQueue rq = MTLRenderQueue.getInstance();
268         rq.lock();
269         try {
270             switch (type) {
271                 case TEXTURE:
272                 case RT_TEXTURE:
273                     // need to make sure the context is current before
274                     // creating the texture
275                     MTLContext.setScratchSurface(graphicsConfig);
276                     break;
277                 default:
278                     break;
279             }
280             rq.flushAndInvokeNow(new Runnable() {
281                 public void run() {
282                     initSurfaceNow(width, height);
283                 }
284             });
285         } finally {
286             rq.unlock();
287         }
288     }
289 
290     /**
291      * Returns the MTLContext for the GraphicsConfig associated with this
292      * surface.
293      */
getContext()294     public final MTLContext getContext() {
295         return graphicsConfig.getContext();
296     }
297 
298     /**
299      * Returns the MTLGraphicsConfig associated with this surface.
300      */
getMTLGraphicsConfig()301     final MTLGraphicsConfig getMTLGraphicsConfig() {
302         return graphicsConfig;
303     }
304 
305     /**
306      * Returns one of the surface type constants defined above.
307      */
getType()308     public final int getType() {
309         return type;
310     }
311 
312     /**
313      * For now, we can only render LCD text if:
314      *   - the fragment shader extension is available, and
315      *   - the source color is opaque, and
316      *   - blending is SrcOverNoEa or disabled
317      *   - and the destination is opaque
318      *
319      * Eventually, we could enhance the native MTL text rendering code
320      * and remove the above restrictions, but that would require significantly
321      * more code just to support a few uncommon cases.
322      */
canRenderLCDText(SunGraphics2D sg2d)323     public boolean canRenderLCDText(SunGraphics2D sg2d) {
324         return
325               sg2d.surfaceData.getTransparency() == Transparency.OPAQUE &&
326               sg2d.paintState <= SunGraphics2D.PAINT_OPAQUECOLOR &&
327              (sg2d.compositeState <= SunGraphics2D.COMP_ISCOPY ||
328              (sg2d.compositeState <= SunGraphics2D.COMP_ALPHA && canHandleComposite(sg2d.composite)));
329     }
330 
canHandleComposite(Composite c)331     private boolean canHandleComposite(Composite c) {
332         if (c instanceof AlphaComposite) {
333             AlphaComposite ac = (AlphaComposite)c;
334 
335             return ac.getRule() == AlphaComposite.SRC_OVER && ac.getAlpha() >= 1f;
336         }
337         return false;
338     }
339 
validatePipe(SunGraphics2D sg2d)340     public void validatePipe(SunGraphics2D sg2d) {
341         TextPipe textpipe;
342         boolean validated = false;
343 
344         // MTLTextRenderer handles both AA and non-AA text, but
345         // only works with the following modes:
346         // (Note: For LCD text we only enter this code path if
347         // canRenderLCDText() has already validated that the mode is
348         // CompositeType.SrcNoEa (opaque color), which will be subsumed
349         // by the CompositeType.SrcNoEa (any color) test below.)
350 
351         if (/* CompositeType.SrcNoEa (any color) */
352                 (sg2d.compositeState <= SunGraphics2D.COMP_ISCOPY &&
353                         sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR)         ||
354 
355                         /* CompositeType.SrcOver (any color) */
356                         (sg2d.compositeState == SunGraphics2D.COMP_ALPHA   &&
357                                 sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR &&
358                                 (((AlphaComposite)sg2d.composite).getRule() ==
359                                         AlphaComposite.SRC_OVER))                                 ||
360 
361                         /* CompositeType.Xor (any color) */
362                         (sg2d.compositeState == SunGraphics2D.COMP_XOR &&
363                                 sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR))
364         {
365             textpipe = mtlTextPipe;
366         } else {
367             // do this to initialize textpipe correctly; we will attempt
368             // to override the non-text pipes below
369             super.validatePipe(sg2d);
370             textpipe = sg2d.textpipe;
371             validated = true;
372         }
373 
374         PixelToParallelogramConverter txPipe = null;
375         MTLRenderer nonTxPipe = null;
376 
377         if (sg2d.antialiasHint != SunHints.INTVAL_ANTIALIAS_ON) {
378             if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) {
379                 if (sg2d.compositeState <= SunGraphics2D.COMP_XOR) {
380                     txPipe = mtlTxRenderPipe;
381                     nonTxPipe = mtlRenderPipe;
382                 }
383             } else if (sg2d.compositeState <= SunGraphics2D.COMP_ALPHA) {
384                 if (MTLPaints.isValid(sg2d)) {
385                     txPipe = mtlTxRenderPipe;
386                     nonTxPipe = mtlRenderPipe;
387                 }
388                 // custom paints handled by super.validatePipe() below
389             }
390         } else {
391             if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) {
392                 if (graphicsConfig.isCapPresent(CAPS_PS30) &&
393                         (sg2d.imageComp == CompositeType.SrcOverNoEa ||
394                                 sg2d.imageComp == CompositeType.SrcOver))
395                 {
396                     if (!validated) {
397                         super.validatePipe(sg2d);
398                         validated = true;
399                     }
400                     PixelToParallelogramConverter aaConverter =
401                             new PixelToParallelogramConverter(sg2d.shapepipe,
402                                     mtlAAPgramPipe,
403                                     1.0/8.0, 0.499,
404                                     false);
405                     sg2d.drawpipe = aaConverter;
406                     sg2d.fillpipe = aaConverter;
407                     sg2d.shapepipe = aaConverter;
408                 } else if (sg2d.compositeState == SunGraphics2D.COMP_XOR) {
409                     // install the solid pipes when AA and XOR are both enabled
410                     txPipe = mtlTxRenderPipe;
411                     nonTxPipe = mtlRenderPipe;
412                 }
413             }
414             // other cases handled by super.validatePipe() below
415         }
416 
417         if (txPipe != null) {
418             if (sg2d.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
419                 sg2d.drawpipe = txPipe;
420                 sg2d.fillpipe = txPipe;
421             } else if (sg2d.strokeState != SunGraphics2D.STROKE_THIN) {
422                 sg2d.drawpipe = txPipe;
423                 sg2d.fillpipe = nonTxPipe;
424             } else {
425                 sg2d.drawpipe = nonTxPipe;
426                 sg2d.fillpipe = nonTxPipe;
427             }
428             // Note that we use the transforming pipe here because it
429             // will examine the shape and possibly perform an optimized
430             // operation if it can be simplified.  The simplifications
431             // will be valid for all STROKE and TRANSFORM types.
432             sg2d.shapepipe = txPipe;
433         } else {
434             if (!validated) {
435                 super.validatePipe(sg2d);
436             }
437         }
438 
439         // install the text pipe based on our earlier decision
440         sg2d.textpipe = textpipe;
441 
442         // always override the image pipe with the specialized MTL pipe
443         sg2d.imagepipe = mtlImagePipe;
444     }
445 
446     @Override
getMaskFill(SunGraphics2D sg2d)447     protected MaskFill getMaskFill(SunGraphics2D sg2d) {
448         if (sg2d.paintState > SunGraphics2D.PAINT_ALPHACOLOR) {
449             /*
450              * We can only accelerate non-Color MaskFill operations if
451              * all of the following conditions hold true:
452              *   - there is an implementation for the given paintState
453              *   - the current Paint can be accelerated for this destination
454              *   - multitexturing is available (since we need to modulate
455              *     the alpha mask texture with the paint texture)
456              *
457              * In all other cases, we return null, in which case the
458              * validation code will choose a more general software-based loop.
459              */
460             if (!MTLPaints.isValid(sg2d) ||
461                     !graphicsConfig.isCapPresent(CAPS_MULTITEXTURE))
462             {
463                 return null;
464             }
465         }
466         return super.getMaskFill(sg2d);
467     }
468 
flush()469     public void flush() {
470         invalidate();
471         MTLRenderQueue rq = MTLRenderQueue.getInstance();
472         rq.lock();
473         try {
474             // make sure we have a current context before
475             // disposing the native resources (e.g. texture object)
476             MTLContext.setScratchSurface(graphicsConfig);
477 
478             RenderBuffer buf = rq.getBuffer();
479             rq.ensureCapacityAndAlignment(12, 4);
480             buf.putInt(FLUSH_SURFACE);
481             buf.putLong(getNativeOps());
482 
483             // this call is expected to complete synchronously, so flush now
484             rq.flushNow();
485         } finally {
486             rq.unlock();
487         }
488     }
489 
isOnScreen()490     public boolean isOnScreen() {
491         return false;
492     }
493 
getMTLTexturePointer(long pData)494     private native long getMTLTexturePointer(long pData);
495 
496     /**
497      * Returns native resource of specified {@code resType} associated with
498      * this surface.
499      *
500      * Specifically, for {@code MTLSurfaceData} this method returns the
501      * the following:
502      * <pre>
503      * TEXTURE              - texture id
504      * </pre>
505      *
506      * Note: the resource returned by this method is only valid on the rendering
507      * thread.
508      *
509      * @return native resource of specified type or 0L if
510      * such resource doesn't exist or can not be retrieved.
511      * @see AccelSurface#getNativeResource
512      */
getNativeResource(int resType)513     public long getNativeResource(int resType) {
514         if (resType == TEXTURE) {
515             return getMTLTexturePointer(getNativeOps());
516         }
517         return 0L;
518     }
519 
getRaster(int x, int y, int w, int h)520     public Raster getRaster(int x, int y, int w, int h) {
521         throw new InternalError("not implemented yet");
522     }
523 
524     @Override
copyArea(SunGraphics2D sg2d, int x, int y, int w, int h, int dx, int dy)525     public boolean copyArea(SunGraphics2D sg2d, int x, int y, int w, int h,
526                             int dx, int dy) {
527         if (sg2d.compositeState >= SunGraphics2D.COMP_XOR) {
528             return false;
529         }
530         mtlRenderPipe.copyArea(sg2d, x, y, w, h, dx, dy);
531         return true;
532     }
533 
getNativeBounds()534     public Rectangle getNativeBounds() {
535         MTLRenderQueue rq = MTLRenderQueue.getInstance();
536         rq.lock();
537         try {
538             return new Rectangle(nativeWidth, nativeHeight);
539         } finally {
540             rq.unlock();
541         }
542     }
543 
544     /**
545      * A surface which implements an intermediate buffer between
546      * the Java2D flusher thread and the AppKit thread.
547      *
548      * This surface serves as a buffer attached to a MTLLayer and
549      * the layer redirects all painting to the buffer's graphics.
550      */
551     public static class MTLLayerSurfaceData extends MTLSurfaceData {
552 
553         private final MTLLayer layer;
554 
MTLLayerSurfaceData(MTLLayer layer, MTLGraphicsConfig gc, int width, int height)555         private MTLLayerSurfaceData(MTLLayer layer, MTLGraphicsConfig gc,
556                                    int width, int height) {
557             super(layer, gc, gc.getColorModel(), RT_TEXTURE, width, height);
558             this.layer = layer;
559             initSurface(this.width, this.height);
560         }
561 
562         @Override
getReplacement()563         public SurfaceData getReplacement() {
564             return layer.getSurfaceData();
565         }
566 
567         @Override
isOnScreen()568         public boolean isOnScreen() {
569             return true;
570         }
571 
572         @Override
getDestination()573         public Object getDestination() {
574             return layer.getDestination();
575         }
576 
577         @Override
getTransparency()578         public int getTransparency() {
579             return layer.getTransparency();
580         }
581 
582         @Override
invalidate()583         public void invalidate() {
584             super.invalidate();
585             clearWindow();
586         }
587     }
588 
589     /**
590      * SurfaceData object representing an off-screen buffer
591      */
592     public static class MTLOffScreenSurfaceData extends MTLSurfaceData {
593         private final Image offscreenImage;
594 
MTLOffScreenSurfaceData(MTLGraphicsConfig gc, int width, int height, Image image, ColorModel cm, int type)595         public MTLOffScreenSurfaceData(MTLGraphicsConfig gc, int width,
596                                        int height, Image image,
597                                        ColorModel cm, int type) {
598             super(null, gc, cm, type, width, height);
599             offscreenImage = image;
600             initSurface(this.width, this.height);
601         }
602 
603         @Override
getReplacement()604         public SurfaceData getReplacement() {
605             return restoreContents(offscreenImage);
606         }
607 
608         /**
609          * Returns destination Image associated with this SurfaceData.
610          */
611         @Override
getDestination()612         public Object getDestination() {
613             return offscreenImage;
614         }
615     }
616 
617 
618     /**
619      * Disposes the native resources associated with the given MTLSurfaceData
620      * (referenced by the pData parameter).  This method is invoked from
621      * the native Dispose() method from the Disposer thread when the
622      * Java-level MTLSurfaceData object is about to go away.
623      */
dispose(long pData, MTLGraphicsConfig gc)624      public static void dispose(long pData, MTLGraphicsConfig gc) {
625         MTLRenderQueue rq = MTLRenderQueue.getInstance();
626         rq.lock();
627         try {
628             // make sure we have a current context before
629             // disposing the native resources (e.g. texture object)
630             MTLContext.setScratchSurface(gc);
631             RenderBuffer buf = rq.getBuffer();
632             rq.ensureCapacityAndAlignment(12, 4);
633             buf.putInt(DISPOSE_SURFACE);
634             buf.putLong(pData);
635 
636             // this call is expected to complete synchronously, so flush now
637             rq.flushNow();
638         } finally {
639             rq.unlock();
640         }
641     }
642 }
643