1 /*
2  * Copyright (c) 2005, 2019, 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.pipe;
27 
28 import java.awt.AlphaComposite;
29 import java.awt.Color;
30 import java.awt.Composite;
31 import java.awt.Paint;
32 import java.awt.geom.AffineTransform;
33 import java.lang.annotation.Native;
34 import java.lang.ref.Reference;
35 import java.lang.ref.WeakReference;
36 
37 import sun.java2d.InvalidPipeException;
38 import sun.java2d.SunGraphics2D;
39 import sun.java2d.loops.XORComposite;
40 import sun.java2d.pipe.hw.AccelSurface;
41 
42 import static sun.java2d.pipe.BufferedOpCodes.BEGIN_SHAPE_CLIP;
43 import static sun.java2d.pipe.BufferedOpCodes.END_SHAPE_CLIP;
44 import static sun.java2d.pipe.BufferedOpCodes.RESET_CLIP;
45 import static sun.java2d.pipe.BufferedOpCodes.RESET_COMPOSITE;
46 import static sun.java2d.pipe.BufferedOpCodes.RESET_TRANSFORM;
47 import static sun.java2d.pipe.BufferedOpCodes.SET_ALPHA_COMPOSITE;
48 import static sun.java2d.pipe.BufferedOpCodes.SET_RECT_CLIP;
49 import static sun.java2d.pipe.BufferedOpCodes.SET_SHAPE_CLIP_SPANS;
50 import static sun.java2d.pipe.BufferedOpCodes.SET_SURFACES;
51 import static sun.java2d.pipe.BufferedOpCodes.SET_TRANSFORM;
52 import static sun.java2d.pipe.BufferedOpCodes.SET_XOR_COMPOSITE;
53 import static sun.java2d.pipe.BufferedRenderPipe.BYTES_PER_SPAN;
54 
55 /**
56  * Base context class for managing state in a single-threaded rendering
57  * environment.  Each state-setting operation (e.g. SET_COLOR) is added to
58  * the provided RenderQueue, which will be processed at a later time by a
59  * single thread.  Note that the RenderQueue lock must be acquired before
60  * calling the validate() method (or any other method in this class).  See
61  * the RenderQueue class comments for a sample usage scenario.
62  *
63  * @see RenderQueue
64  */
65 public abstract class BufferedContext {
66 
67     /*
68      * The following flags help the internals of validate() determine
69      * the appropriate (meaning correct, or optimal) code path when
70      * setting up the current context.  The flags can be bitwise OR'd
71      * together as needed.
72      */
73 
74     /**
75      * Indicates that no flags are needed; take all default code paths.
76      */
77     @Native public static final int NO_CONTEXT_FLAGS = (0 << 0);
78     /**
79      * Indicates that the source surface (or color value, if it is a simple
80      * rendering operation) is opaque (has an alpha value of 1.0).  If this
81      * flag is present, it allows us to disable blending in certain
82      * situations in order to improve performance.
83      */
84     @Native public static final int SRC_IS_OPAQUE    = (1 << 0);
85     /**
86      * Indicates that the operation uses an alpha mask, which may determine
87      * the code path that is used when setting up the current paint state.
88      */
89     @Native public static final int USE_MASK         = (1 << 1);
90 
91     private final RenderQueue rq;
92     private final RenderBuffer buf;
93 
94     /**
95      * This is a reference to the most recently validated BufferedContext.  If
96      * this value is null, it means that there is no current context.  It is
97      * provided here so that validate() only needs to do a quick reference
98      * check to see if the BufferedContext passed to that method is the same
99      * as the one we've cached here.
100      */
101     protected static BufferedContext currentContext;
102 
103     private Reference<AccelSurface> validSrcDataRef = new WeakReference<>(null);
104     private Reference<AccelSurface> validDstDataRef = new WeakReference<>(null);
105     private Reference<Region> validClipRef = new WeakReference<>(null);
106     private Reference<Composite> validCompRef = new WeakReference<>(null);
107     private Reference<Paint> validPaintRef = new WeakReference<>(null);
108     // renamed from isValidatedPaintAColor as part of a work around for 6764257
109     private boolean         isValidatedPaintJustAColor;
110     private int             validatedRGB;
111     private int             validatedFlags;
112     private boolean         xformInUse;
113     private AffineTransform transform;
114 
BufferedContext(RenderQueue rq)115     protected BufferedContext(RenderQueue rq) {
116         this.rq = rq;
117         this.buf = rq.getBuffer();
118     }
119 
120     /**
121      * Fetches the BufferedContextContext associated with the dst. surface
122      * and validates the context using the given parameters.  Most rendering
123      * operations will call this method first in order to set the necessary
124      * state before issuing rendering commands.
125      *
126      * Note: must be called while the RenderQueue lock is held.
127      *
128      * It's assumed that the type of surfaces has been checked by the Renderer
129      *
130      * @throws InvalidPipeException if either src or dest surface is not valid
131      * or lost
132      * @see RenderQueue#lock
133      * @see RenderQueue#unlock
134      */
validateContext(AccelSurface srcData, AccelSurface dstData, Region clip, Composite comp, AffineTransform xform, Paint paint, SunGraphics2D sg2d, int flags)135     public static void validateContext(AccelSurface srcData,
136                                        AccelSurface dstData,
137                                        Region clip, Composite comp,
138                                        AffineTransform xform,
139                                        Paint paint, SunGraphics2D sg2d,
140                                        int flags)
141     {
142         // assert rq.lock.isHeldByCurrentThread();
143         BufferedContext context = dstData.getContext();
144         context.validate(srcData, dstData,
145                          clip, comp, xform, paint, sg2d, flags);
146     }
147 
148     /**
149      * Fetches the BufferedContextassociated with the surface
150      * and disables all context state settings.
151      *
152      * Note: must be called while the RenderQueue lock is held.
153      *
154      * It's assumed that the type of surfaces has been checked by the Renderer
155      *
156      * @throws InvalidPipeException if the surface is not valid
157      * or lost
158      * @see RenderQueue#lock
159      * @see RenderQueue#unlock
160      */
validateContext(AccelSurface surface)161     public static void validateContext(AccelSurface surface) {
162         // assert rt.lock.isHeldByCurrentThread();
163         validateContext(surface, surface,
164                         null, null, null, null, null, NO_CONTEXT_FLAGS);
165     }
166 
167     /**
168      * Validates the given parameters against the current state for this
169      * context.  If this context is not current, it will be made current
170      * for the given source and destination surfaces, and the viewport will
171      * be updated.  Then each part of the context state (clip, composite,
172      * etc.) is checked against the previous value.  If the value has changed
173      * since the last call to validate(), it will be updated accordingly.
174      *
175      * Note that the SunGraphics2D parameter is only used for the purposes
176      * of validating a (non-null) Paint parameter.  In all other cases it
177      * is safe to pass a null SunGraphics2D and it will be ignored.
178      *
179      * Note: must be called while the RenderQueue lock is held.
180      *
181      * It's assumed that the type of surfaces has been checked by the Renderer
182      *
183      * @throws InvalidPipeException if either src or dest surface is not valid
184      * or lost
185      */
validate(AccelSurface srcData, AccelSurface dstData, Region clip, Composite comp, AffineTransform xform, Paint paint, SunGraphics2D sg2d, int flags)186     private void validate(AccelSurface srcData, AccelSurface dstData,
187                          Region clip, Composite comp,
188                          AffineTransform xform,
189                          Paint paint, SunGraphics2D sg2d, int flags)
190     {
191         // assert rq.lock.isHeldByCurrentThread();
192 
193         boolean updateClip = false;
194         boolean updatePaint = false;
195 
196         if (!dstData.isValid() ||
197             dstData.isSurfaceLost() || srcData.isSurfaceLost())
198         {
199             invalidateContext();
200             throw new InvalidPipeException("bounds changed or surface lost");
201         }
202 
203         if (paint instanceof Color) {
204             // REMIND: not 30-bit friendly
205             int newRGB = ((Color)paint).getRGB();
206             if (isValidatedPaintJustAColor) {
207                 if (newRGB != validatedRGB) {
208                     validatedRGB = newRGB;
209                     updatePaint = true;
210                 }
211             } else {
212                 validatedRGB = newRGB;
213                 updatePaint = true;
214                 isValidatedPaintJustAColor = true;
215             }
216         } else if (validPaintRef.get() != paint) {
217             updatePaint = true;
218             // this should be set when we are switching from paint to color
219             // in which case this condition will be true
220             isValidatedPaintJustAColor = false;
221         }
222 
223         final AccelSurface validatedSrcData = validSrcDataRef.get();
224         final AccelSurface validatedDstData = validDstDataRef.get();
225         if ((currentContext != this) ||
226             (srcData != validatedSrcData) ||
227             (dstData != validatedDstData))
228         {
229             if (dstData != validatedDstData) {
230                 // the clip is dependent on the destination surface, so we
231                 // need to update it if we have a new destination surface
232                 updateClip = true;
233             }
234 
235             if (paint == null) {
236                 // make sure we update the color state (otherwise, it might
237                 // not be updated if this is the first time the context
238                 // is being validated)
239                 updatePaint = true;
240             }
241 
242             // update the current source and destination surfaces
243             setSurfaces(srcData, dstData);
244 
245             currentContext = this;
246             validSrcDataRef = new WeakReference<>(srcData);
247             validDstDataRef = new WeakReference<>(dstData);
248         }
249 
250         // validate clip
251         final Region validatedClip = validClipRef.get();
252         if ((clip != validatedClip) || updateClip) {
253             if (clip != null) {
254                 if (updateClip ||
255                     validatedClip == null ||
256                     !(validatedClip.isRectangular() && clip.isRectangular()) ||
257                     ((clip.getLoX() != validatedClip.getLoX() ||
258                       clip.getLoY() != validatedClip.getLoY() ||
259                       clip.getHiX() != validatedClip.getHiX() ||
260                       clip.getHiY() != validatedClip.getHiY())))
261                 {
262                     setClip(clip);
263                 }
264             } else {
265                 resetClip();
266             }
267             validClipRef = new WeakReference<>(clip);
268         }
269 
270         // validate composite (note that a change in the context flags
271         // may require us to update the composite state, even if the
272         // composite has not changed)
273         if ((comp != validCompRef.get()) || (flags != validatedFlags)) {
274             if (comp != null) {
275                 setComposite(comp, flags);
276             } else {
277                 resetComposite();
278             }
279             // the paint state is dependent on the composite state, so make
280             // sure we update the color below
281             updatePaint = true;
282             validCompRef = new WeakReference<>(comp);
283             validatedFlags = flags;
284         }
285 
286         // validate transform
287         boolean txChanged = false;
288         if (xform == null) {
289             if (xformInUse) {
290                 resetTransform();
291                 xformInUse = false;
292                 txChanged = true;
293             } else if (sg2d != null && !sg2d.transform.equals(transform)) {
294                 txChanged = true;
295             }
296             if (sg2d != null && txChanged) {
297                 transform = new AffineTransform(sg2d.transform);
298             }
299         } else {
300             setTransform(xform);
301             xformInUse = true;
302             txChanged = true;
303         }
304         // non-Color paints may require paint revalidation
305         if (!isValidatedPaintJustAColor && txChanged) {
306             updatePaint = true;
307         }
308 
309         // validate paint
310         if (updatePaint) {
311             if (paint != null) {
312                 BufferedPaints.setPaint(rq, sg2d, paint, flags);
313             } else {
314                 BufferedPaints.resetPaint(rq);
315             }
316             validPaintRef = new WeakReference<>(paint);
317         }
318 
319         // mark dstData dirty
320         // REMIND: is this really needed now? we do it in SunGraphics2D..
321         dstData.markDirty();
322     }
323 
setSurfaces(AccelSurface srcData, AccelSurface dstData)324     private void setSurfaces(AccelSurface srcData,
325                              AccelSurface dstData)
326     {
327         // assert rq.lock.isHeldByCurrentThread();
328         rq.ensureCapacityAndAlignment(20, 4);
329         buf.putInt(SET_SURFACES);
330         buf.putLong(srcData.getNativeOps());
331         buf.putLong(dstData.getNativeOps());
332     }
333 
resetClip()334     private void resetClip() {
335         // assert rq.lock.isHeldByCurrentThread();
336         rq.ensureCapacity(4);
337         buf.putInt(RESET_CLIP);
338     }
339 
setClip(Region clip)340     private void setClip(Region clip) {
341         // assert rq.lock.isHeldByCurrentThread();
342         if (clip.isRectangular()) {
343             rq.ensureCapacity(20);
344             buf.putInt(SET_RECT_CLIP);
345             buf.putInt(clip.getLoX()).putInt(clip.getLoY());
346             buf.putInt(clip.getHiX()).putInt(clip.getHiY());
347         } else {
348             rq.ensureCapacity(28); // so that we have room for at least a span
349             buf.putInt(BEGIN_SHAPE_CLIP);
350             buf.putInt(SET_SHAPE_CLIP_SPANS);
351             // include a placeholder for the span count
352             int countIndex = buf.position();
353             buf.putInt(0);
354             int spanCount = 0;
355             int remainingSpans = buf.remaining() / BYTES_PER_SPAN;
356             int[] span = new int[4];
357             SpanIterator si = clip.getSpanIterator();
358             while (si.nextSpan(span)) {
359                 if (remainingSpans == 0) {
360                     buf.putInt(countIndex, spanCount);
361                     rq.flushNow();
362                     buf.putInt(SET_SHAPE_CLIP_SPANS);
363                     countIndex = buf.position();
364                     buf.putInt(0);
365                     spanCount = 0;
366                     remainingSpans = buf.remaining() / BYTES_PER_SPAN;
367                 }
368                 buf.putInt(span[0]); // x1
369                 buf.putInt(span[1]); // y1
370                 buf.putInt(span[2]); // x2
371                 buf.putInt(span[3]); // y2
372                 spanCount++;
373                 remainingSpans--;
374             }
375             buf.putInt(countIndex, spanCount);
376             rq.ensureCapacity(4);
377             buf.putInt(END_SHAPE_CLIP);
378         }
379     }
380 
resetComposite()381     private void resetComposite() {
382         // assert rq.lock.isHeldByCurrentThread();
383         rq.ensureCapacity(4);
384         buf.putInt(RESET_COMPOSITE);
385     }
386 
setComposite(Composite comp, int flags)387     private void setComposite(Composite comp, int flags) {
388         // assert rq.lock.isHeldByCurrentThread();
389         if (comp instanceof AlphaComposite) {
390             AlphaComposite ac = (AlphaComposite)comp;
391             rq.ensureCapacity(16);
392             buf.putInt(SET_ALPHA_COMPOSITE);
393             buf.putInt(ac.getRule());
394             buf.putFloat(ac.getAlpha());
395             buf.putInt(flags);
396         } else if (comp instanceof XORComposite) {
397             int xorPixel = ((XORComposite)comp).getXorPixel();
398             rq.ensureCapacity(8);
399             buf.putInt(SET_XOR_COMPOSITE);
400             buf.putInt(xorPixel);
401         } else {
402             throw new InternalError("not yet implemented");
403         }
404     }
405 
resetTransform()406     private void resetTransform() {
407         // assert rq.lock.isHeldByCurrentThread();
408         rq.ensureCapacity(4);
409         buf.putInt(RESET_TRANSFORM);
410     }
411 
setTransform(AffineTransform xform)412     private void setTransform(AffineTransform xform) {
413         // assert rq.lock.isHeldByCurrentThread();
414         rq.ensureCapacityAndAlignment(52, 4);
415         buf.putInt(SET_TRANSFORM);
416         buf.putDouble(xform.getScaleX());
417         buf.putDouble(xform.getShearY());
418         buf.putDouble(xform.getShearX());
419         buf.putDouble(xform.getScaleY());
420         buf.putDouble(xform.getTranslateX());
421         buf.putDouble(xform.getTranslateY());
422     }
423 
424     /**
425      * Resets this context's surfaces and all attributes.
426      *
427      * Note: must be called while the RenderQueue lock is held.
428      *
429      * @see RenderQueue#lock
430      * @see RenderQueue#unlock
431      */
invalidateContext()432     public final void invalidateContext() {
433         resetTransform();
434         resetComposite();
435         resetClip();
436         BufferedPaints.resetPaint(rq);
437         validSrcDataRef.clear();
438         validDstDataRef.clear();
439         validCompRef.clear();
440         validClipRef.clear();
441         validPaintRef.clear();
442         isValidatedPaintJustAColor = false;
443         xformInUse = false;
444     }
445 
446     /**
447      * Returns a singleton {@code RenderQueue} object used by the rendering
448      * pipeline.
449      *
450      * @return a render queue
451      * @see RenderQueue
452      */
getRenderQueue()453     public final RenderQueue getRenderQueue() {
454         return rq;
455     }
456 }
457