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#include "EncoderManager.h"
27#include "MTLContext.h"
28#include "sun_java2d_SunGraphics2D.h"
29#import "common.h"
30
31// NOTE: uncomment to disable comparing cached encoder states with requested (for debugging)
32// #define ALWAYS_UPDATE_ENCODER_STATES
33
34const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
35
36// Internal utility class that represents the set of 'mutable' encoder properties
37@interface EncoderStates : NSObject
38@property (readonly) MTLClip * clip;
39
40- (id)init;
41- (void)dealloc;
42
43- (void)reset:(id<MTLTexture>)destination
44           isDstOpaque:(jboolean)isDstOpaque
45    isDstPremultiplied:(jboolean)isDstPremultiplied
46                  isAA:(jboolean)isAA
47                  isText:(jboolean)isText
48                  isLCD:(jboolean)isLCD;
49
50- (void)updateEncoder:(id<MTLRenderCommandEncoder>)encoder
51              context:(MTLContext *)mtlc
52        renderOptions:(const RenderOptions *)renderOptions
53          forceUpdate:(jboolean)forceUpdate;
54@property (assign) jboolean aa;
55@property (assign) jboolean text;
56@property (assign) jboolean lcd;
57@property (assign) jboolean aaShader;
58@property (retain) MTLPaint* paint;
59@end
60
61@implementation EncoderStates {
62    MTLPipelineStatesStorage * _pipelineStateStorage;
63    id<MTLDevice> _device;
64
65    // Persistent encoder properties
66    id<MTLTexture> _destination;
67    SurfaceRasterFlags _dstFlags;
68
69    jboolean _isAA;
70    jboolean _isText;
71    jboolean _isLCD;
72    jboolean _isAAShader;
73
74    //
75    // Cached 'mutable' states of encoder
76    //
77
78    // Composite rule and source raster flags (it affects the CAD-multipliers (of pipelineState))
79    MTLComposite * _composite;
80    SurfaceRasterFlags _srcFlags;
81
82    // Paint mode (it affects shaders (of pipelineState) and corresponding buffers)
83    MTLPaint * _paint;
84
85    // If true, indicates that encoder is used for texture drawing (user must do [encoder setFragmentTexture:] before drawing)
86    jboolean _isTexture;
87    int _interpolationMode;
88
89    // Clip rect or stencil
90    MTLClip * _clip;
91
92    // Transform (affects transformation inside vertex shader)
93    MTLTransform * _transform;
94}
95@synthesize aa = _isAA;
96@synthesize text = _isText;
97@synthesize lcd = _isLCD;
98@synthesize aaShader = _isAAShader;
99@synthesize paint = _paint;
100
101- (id)init {
102    self = [super init];
103    if (self) {
104        _destination = nil;
105        _composite = [[MTLComposite alloc] init];
106        _paint = [[MTLPaint alloc] init];
107        _transform = [[MTLTransform alloc] init];
108        _clip = [[MTLClip alloc] init];
109    }
110    return self;
111}
112
113- (void)dealloc {
114    [_composite release];
115    [_paint release];
116    [_transform release];
117    [super dealloc];
118}
119
120- (void)setContext:(MTLContext * _Nonnull)mtlc {
121    self->_pipelineStateStorage = mtlc.pipelineStateStorage;
122    self->_device = mtlc.device;
123}
124
125- (void)reset:(id<MTLTexture>)destination
126           isDstOpaque:(jboolean)isDstOpaque
127    isDstPremultiplied:(jboolean)isDstPremultiplied
128                  isAA:(jboolean)isAA
129                  isText:(jboolean)isText
130                  isLCD:(jboolean)isLCD {
131    _destination = destination;
132    _dstFlags.isOpaque = isDstOpaque;
133    _dstFlags.isPremultiplied = isDstPremultiplied;
134    _isAA = isAA;
135    _isText = isText;
136    _isLCD = isLCD;
137    // NOTE: probably it's better to invalidate/reset all cached states now
138}
139
140- (void)updateEncoder:(id<MTLRenderCommandEncoder>)encoder
141              context:(MTLContext *)mtlc
142        renderOptions:(const RenderOptions *)renderOptions
143          forceUpdate:(jboolean)forceUpdate
144{
145    // 1. Process special case for stencil mask generation
146    if (mtlc.clip.stencilMaskGenerationInProgress == JNI_TRUE) {
147        // use separate pipeline state for stencil generation
148        if (forceUpdate || (_clip.stencilMaskGenerationInProgress != JNI_TRUE)) {
149            [_clip copyFrom:mtlc.clip];
150            [_clip setMaskGenerationPipelineState:encoder
151                                        destWidth:_destination.width
152                                       destHeight:_destination.height
153                             pipelineStateStorage:_pipelineStateStorage];
154        }
155
156        [self updateTransform:encoder transform:mtlc.transform forceUpdate:forceUpdate];
157        return;
158    }
159
160    // 2. Otherwise update all 'mutable' properties of encoder
161    [self updatePipelineState:encoder
162                      context:mtlc
163                renderOptions:renderOptions
164                  forceUpdate:forceUpdate];
165    [self updateTransform:encoder transform:mtlc.transform forceUpdate:forceUpdate];
166    [self updateClip:encoder clip:mtlc.clip forceUpdate:forceUpdate];
167}
168
169//
170// Internal methods that update states when necessary (compare with cached states)
171//
172
173// Updates pipelineState (and corresponding buffers) with use of paint+composite+flags
174- (void)updatePipelineState:(id<MTLRenderCommandEncoder>)encoder
175                    context:(MTLContext *)mtlc
176              renderOptions:(const RenderOptions *)renderOptions
177                forceUpdate:(jboolean)forceUpdate
178{
179    if (!forceUpdate
180        && [_paint isEqual:mtlc.paint]
181        && [_composite isEqual:mtlc.composite]
182        && (_isTexture == renderOptions->isTexture && (!renderOptions->isTexture || _interpolationMode == renderOptions->interpolation)) // interpolation is used only in texture mode
183        && _isAA == renderOptions->isAA
184        && _isAAShader == renderOptions->isAAShader
185        && _isText == renderOptions->isText
186        && _isLCD == renderOptions->isLCD
187        && _srcFlags.isOpaque == renderOptions->srcFlags.isOpaque && _srcFlags.isPremultiplied == renderOptions->srcFlags.isPremultiplied)
188        return;
189
190    self.paint = mtlc.paint;
191    [_composite copyFrom:mtlc.composite];
192    _isTexture = renderOptions->isTexture;
193    _interpolationMode = renderOptions->interpolation;
194    _isAA = renderOptions->isAA;
195    _isAAShader = renderOptions->isAAShader;
196    _isText = renderOptions->isText;
197    _isLCD = renderOptions->isLCD;
198    _srcFlags = renderOptions->srcFlags;
199
200    if ((jint)[mtlc.composite getCompositeState] == sun_java2d_SunGraphics2D_COMP_XOR) {
201
202        [mtlc.paint setXorModePipelineState:encoder
203                               context:mtlc
204                         renderOptions:renderOptions
205                  pipelineStateStorage:_pipelineStateStorage];
206    } else {
207        [mtlc.paint  setPipelineState:encoder
208                              context:mtlc
209                        renderOptions:renderOptions
210                 pipelineStateStorage:_pipelineStateStorage];
211    }
212}
213
214- (void) updateClip:(id<MTLRenderCommandEncoder>)encoder clip:(MTLClip *)clip forceUpdate:(jboolean)forceUpdate
215{
216    if (clip.stencilMaskGenerationInProgress == JNI_TRUE) {
217        // don't set setScissorOrStencil when generation in progress
218        return;
219    }
220
221    if (!forceUpdate && [_clip isEqual:clip])
222        return;
223
224    [_clip copyFrom:clip];
225    [_clip setScissorOrStencil:encoder
226                     destWidth:_destination.width
227                    destHeight:_destination.height
228                        device:_device];
229}
230
231- (void)updateTransform:(id <MTLRenderCommandEncoder>)encoder
232              transform:(MTLTransform *)transform
233            forceUpdate:(jboolean)forceUpdate
234{
235    if (!forceUpdate
236        && [_transform isEqual:transform])
237        return;
238
239    [_transform copyFrom:transform];
240    [_transform setVertexMatrix:encoder
241                        destWidth:_destination.width
242                       destHeight:_destination.height];
243}
244
245@end
246
247@implementation EncoderManager {
248    MTLContext * _mtlc; // used to obtain CommandBufferWrapper and Composite/Paint/Transform
249
250    id<MTLRenderCommandEncoder> _encoder;
251
252    // 'Persistent' properties of encoder
253    id<MTLTexture> _destination;
254    id<MTLTexture> _aaDestination;
255    BOOL _useStencil;
256
257    // 'Mutable' states of encoder
258    EncoderStates * _encoderStates;
259}
260
261- (id _Nonnull)init {
262    self = [super init];
263    if (self) {
264        _encoder = nil;
265        _destination = nil;
266        _aaDestination = nil;
267        _useStencil = NO;
268        _encoderStates = [[EncoderStates alloc] init];
269
270    }
271    return self;
272}
273
274- (void)dealloc {
275    [_encoderStates release];
276    [super dealloc];
277}
278
279- (void)setContext:(MTLContex * _Nonnull)mtlc {
280    self->_mtlc = mtlc;
281    [self->_encoderStates setContext:mtlc];
282}
283
284- (id<MTLRenderCommandEncoder> _Nonnull) getRenderEncoder:(const BMTLSDOps * _Nonnull)dstOps
285{
286    return [self getRenderEncoder:dstOps->pTexture isDstOpaque:dstOps->isOpaque];
287}
288
289- (id<MTLRenderCommandEncoder> _Nonnull)getAARenderEncoder:(const BMTLSDOps * _Nonnull)dstOps {
290  id<MTLTexture> dstTxt = dstOps->pTexture;
291  RenderOptions roptions = {JNI_FALSE, JNI_TRUE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, {dstOps->isOpaque, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_FALSE};
292  return [self getEncoder:dstTxt renderOptions:&roptions];
293}
294
295- (id<MTLRenderCommandEncoder> _Nonnull)getAAShaderRenderEncoder:(const BMTLSDOps * _Nonnull)dstOps
296{
297    RenderOptions roptions = {JNI_FALSE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, {dstOps->isOpaque, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_TRUE};
298    return [self getEncoder:dstOps->pTexture renderOptions:&roptions];
299}
300
301- (id<MTLRenderCommandEncoder> _Nonnull)getRenderEncoder:(id<MTLTexture> _Nonnull)dest
302                                             isDstOpaque:(bool)isOpaque
303{
304    RenderOptions roptions = {JNI_FALSE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, {isOpaque, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_FALSE};
305    return [self getEncoder:dest renderOptions:&roptions];
306}
307
308- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(const BMTLSDOps * _Nonnull)dstOps
309                                      isSrcOpaque:(bool)isSrcOpaque
310{
311    return [self getTextureEncoder:dstOps->pTexture
312                       isSrcOpaque:isSrcOpaque
313                       isDstOpaque:dstOps->isOpaque
314                     interpolation:INTERPOLATION_NEAREST_NEIGHBOR];
315}
316
317- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
318                                               isSrcOpaque:(bool)isSrcOpaque
319                                               isDstOpaque:(bool)isDstOpaque
320{
321    return [self getTextureEncoder:dest
322                       isSrcOpaque:isSrcOpaque
323                       isDstOpaque:isDstOpaque
324                     interpolation:INTERPOLATION_NEAREST_NEIGHBOR
325                              isAA:JNI_FALSE];
326}
327
328- (id<MTLRenderCommandEncoder> _Nonnull) getLCDEncoder:(id<MTLTexture> _Nonnull)dest
329                                               isSrcOpaque:(bool)isSrcOpaque
330                                               isDstOpaque:(bool)isDstOpaque
331{
332    RenderOptions roptions = {JNI_TRUE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, {isSrcOpaque, JNI_TRUE }, {isDstOpaque, JNI_TRUE}, JNI_FALSE, JNI_TRUE, JNI_FALSE};
333    return [self getEncoder:dest renderOptions:&roptions];
334}
335
336- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
337                                      isSrcOpaque:(bool)isSrcOpaque
338                                      isDstOpaque:(bool)isDstOpaque
339                                    interpolation:(int)interpolation
340                                             isAA:(jboolean)isAA
341{
342    RenderOptions roptions = {JNI_TRUE, isAA, interpolation, { isSrcOpaque, JNI_TRUE }, {isDstOpaque, JNI_TRUE}, JNI_FALSE, JNI_FALSE, JNI_FALSE};
343    return [self getEncoder:dest renderOptions:&roptions];
344}
345
346- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
347                                               isSrcOpaque:(bool)isSrcOpaque
348                                               isDstOpaque:(bool)isDstOpaque
349                                             interpolation:(int)interpolation
350{
351    return [self getTextureEncoder:dest isSrcOpaque:isSrcOpaque isDstOpaque:isDstOpaque interpolation:interpolation isAA:JNI_FALSE];
352}
353
354- (id<MTLRenderCommandEncoder> _Nonnull) getTextEncoder:(const BMTLSDOps * _Nonnull)dstOps
355                                      isSrcOpaque:(bool)isSrcOpaque
356{
357    RenderOptions roptions = {JNI_TRUE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, { isSrcOpaque, JNI_TRUE }, {dstOps->isOpaque, JNI_TRUE}, JNI_TRUE, JNI_FALSE, JNI_FALSE};
358    return [self getEncoder:dstOps->pTexture renderOptions:&roptions];
359}
360
361- (id<MTLRenderCommandEncoder> _Nonnull) getEncoder:(id <MTLTexture> _Nonnull)dest
362                                      renderOptions:(const RenderOptions * _Nonnull)renderOptions
363{
364  //
365  // 1. check whether it's necessary to call endEncoder
366  //
367  jboolean needEnd = JNI_FALSE;
368  if (_encoder != nil) {
369    if (_destination != dest || renderOptions->isAA != _encoderStates.aa) {
370      J2dTraceLn2(J2D_TRACE_VERBOSE,
371                  "end common encoder because of dest change: %p -> %p",
372                  _destination, dest);
373      needEnd = JNI_TRUE;
374    } else if ((_useStencil == NO) != ([_mtlc.clip isShape] == NO)) {
375      // 1. When mode changes RECT -> SHAPE we must recreate encoder with
376      // stencilAttachment (todo: consider the case when current encoder already
377      // has stencil)
378      //
379      // 2. When mode changes SHAPE -> RECT it seems that we can use the same
380      // encoder with disabled stencil test, but [encoder
381      // setDepthStencilState:nil] causes crash, so we have to recreate encoder
382      // in such case
383      J2dTraceLn2(J2D_TRACE_VERBOSE,
384                  "end common encoder because toggle stencil: %d -> %d",
385                  (int)_useStencil, (int)[_mtlc.clip isShape]);
386      needEnd = JNI_TRUE;
387    }
388  }
389  if (needEnd)
390    [self endEncoder];
391
392  //
393  // 2. recreate encoder if necessary
394  //
395  jboolean forceUpdate = JNI_FALSE;
396#ifdef ALWAYS_UPDATE_ENCODER_STATES
397  forceUpdate = JNI_TRUE;
398#endif // ALWAYS_UPDATE_ENCODER_STATES
399
400  if (_encoder == nil) {
401    _destination = dest;
402    _useStencil = [_mtlc.clip isShape] && !_mtlc.clip.stencilMaskGenerationInProgress;
403    forceUpdate = JNI_TRUE;
404
405    MTLCommandBufferWrapper *cbw = [_mtlc getCommandBufferWrapper];
406    MTLRenderPassDescriptor *rpd =
407        [MTLRenderPassDescriptor renderPassDescriptor];
408    MTLRenderPassColorAttachmentDescriptor *ca = rpd.colorAttachments[0];
409    ca.texture = dest;
410
411    // TODO: Find out why we cannot use
412    // if (_mtlc.clip.stencilMaskGenerationInProgress == YES) {
413    //     ca.loadAction = MTLLoadActionClear;
414    //     ca.clearColor = MTLClearColorMake(0.0f, 0.0f,0.0f, 0.0f);
415    // }
416    // here to avoid creation of clearEncoder in beginShapeClip
417
418    ca.loadAction = MTLLoadActionLoad;
419    ca.storeAction = MTLStoreActionStore;
420
421    if (_useStencil && !renderOptions->isAA) {
422        // If you enable stencil testing or stencil writing, the
423        // MTLRenderPassDescriptor must include a stencil attachment.
424        rpd.stencilAttachment.loadAction = MTLLoadActionLoad;
425        rpd.stencilAttachment.storeAction = MTLStoreActionStore;
426        rpd.stencilAttachment.texture = _mtlc.clip.stencilTextureRef;
427    } else if (_mtlc.clip.stencilMaskGenerationInProgress == YES) {
428        rpd.stencilAttachment.texture = _mtlc.clip.dstOps->pStencilTexture;
429        rpd.stencilAttachment.clearStencil = 0;
430        rpd.stencilAttachment.loadAction = _mtlc.clip.stencilMaskGenerationStarted? MTLLoadActionLoad : MTLLoadActionClear;
431        _mtlc.clip.stencilMaskGenerationStarted = YES;
432        rpd.stencilAttachment.storeAction = MTLStoreActionStore;
433    }
434
435    // J2dTraceLn1(J2D_TRACE_VERBOSE, "created render encoder to draw on
436    // tex=%p", dest);
437    _encoder = [[cbw getCommandBuffer] renderCommandEncoderWithDescriptor:rpd];
438
439    [_encoderStates reset:dest
440               isDstOpaque:renderOptions->dstFlags.isOpaque
441        isDstPremultiplied:YES
442                      isAA:renderOptions->isAA
443                      isText:renderOptions->isText
444                      isLCD:renderOptions->isLCD];
445  }
446
447  //
448  // 3. update encoder states
449  //
450  [_encoderStates updateEncoder:_encoder
451                        context:_mtlc
452                  renderOptions:renderOptions
453                    forceUpdate:forceUpdate];
454
455  return _encoder;
456}
457
458- (id<MTLBlitCommandEncoder> _Nonnull) createBlitEncoder {
459    [self endEncoder];
460    return [[[_mtlc getCommandBufferWrapper] getCommandBuffer] blitCommandEncoder];
461}
462
463- (void) endEncoder {
464    if (_encoder != nil) {
465      [_encoder endEncoding];
466      _encoder = nil;
467      _destination = nil;
468    }
469}
470
471@end
472