blob: 95374d2c93e53f1d4af0bd960b70e9b7be584bee [file]
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "EncoderManager.h"
#include "MTLContext.h"
#include "sun_java2d_SunGraphics2D.h"
#import "common.h"
// NOTE: uncomment to disable comparing cached encoder states with requested (for debugging)
// #define ALWAYS_UPDATE_ENCODER_STATES
const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
// Internal utility class that represents the set of 'mutable' encoder properties
@interface EncoderStates : NSObject
@property (readonly) MTLClip * clip;
- (id)init;
- (void)dealloc;
- (void)reset:(id<MTLTexture>)destination
isAA:(jboolean)isAA
isText:(jboolean)isText
isLCD:(jboolean)isLCD;
- (void)updateEncoder:(id<MTLRenderCommandEncoder>)encoder
context:(MTLContext *)mtlc
renderOptions:(const RenderOptions *)renderOptions
forceUpdate:(jboolean)forceUpdate;
@property (assign) jboolean aa;
@property (assign) jboolean text;
@property (assign) jboolean lcd;
@property (assign) jboolean aaShader;
@property (retain) MTLPaint* paint;
@end
@implementation EncoderStates {
MTLPipelineStatesStorage * _pipelineStateStorage;
id<MTLDevice> _device;
// Persistent encoder properties
id<MTLTexture> _destination;
jboolean _isAA;
jboolean _isText;
jboolean _isLCD;
jboolean _isAAShader;
//
// Cached 'mutable' states of encoder
//
// Composite rule and source raster flags (it affects the CAD-multipliers (of pipelineState))
MTLComposite * _composite;
SurfaceRasterFlags _srcFlags;
// Paint mode (it affects shaders (of pipelineState) and corresponding buffers)
MTLPaint * _paint;
// If true, indicates that encoder is used for texture drawing (user must do [encoder setFragmentTexture:] before drawing)
jboolean _isTexture;
int _interpolationMode;
// Clip rect or stencil
MTLClip * _clip;
// Transform (affects transformation inside vertex shader)
MTLTransform * _transform;
}
@synthesize aa = _isAA;
@synthesize text = _isText;
@synthesize lcd = _isLCD;
@synthesize aaShader = _isAAShader;
@synthesize paint = _paint;
- (id)init {
self = [super init];
if (self) {
_destination = nil;
_composite = [[MTLComposite alloc] init];
_paint = [[MTLPaint alloc] init];
_transform = [[MTLTransform alloc] init];
_clip = [[MTLClip alloc] init];
}
return self;
}
- (void)dealloc {
[_composite release];
[_paint release];
[_transform release];
[super dealloc];
}
- (void)setContext:(MTLContext * _Nonnull)mtlc {
self->_pipelineStateStorage = mtlc.pipelineStateStorage;
self->_device = mtlc.device;
}
- (void)reset:(id<MTLTexture>)destination
isAA:(jboolean)isAA
isText:(jboolean)isText
isLCD:(jboolean)isLCD {
_destination = destination;
_isAA = isAA;
_isText = isText;
_isLCD = isLCD;
// NOTE: probably it's better to invalidate/reset all cached states now
}
- (void)updateEncoder:(id<MTLRenderCommandEncoder>)encoder
context:(MTLContext *)mtlc
renderOptions:(const RenderOptions *)renderOptions
forceUpdate:(jboolean)forceUpdate
{
// 1. Process special case for stencil mask generation
if (mtlc.clip.stencilMaskGenerationInProgress == JNI_TRUE) {
// use separate pipeline state for stencil generation
if (forceUpdate || (_clip.stencilMaskGenerationInProgress != JNI_TRUE)) {
[_clip copyFrom:mtlc.clip];
[_clip setMaskGenerationPipelineState:encoder
destWidth:_destination.width
destHeight:_destination.height
pipelineStateStorage:_pipelineStateStorage];
}
[self updateTransform:encoder transform:mtlc.transform forceUpdate:forceUpdate];
return;
}
// 2. Otherwise update all 'mutable' properties of encoder
[self updatePipelineState:encoder
context:mtlc
renderOptions:renderOptions
forceUpdate:forceUpdate];
[self updateTransform:encoder transform:mtlc.transform forceUpdate:forceUpdate];
[self updateClip:encoder clip:mtlc.clip forceUpdate:forceUpdate];
}
//
// Internal methods that update states when necessary (compare with cached states)
//
// Updates pipelineState (and corresponding buffers) with use of paint+composite+flags
- (void)updatePipelineState:(id<MTLRenderCommandEncoder>)encoder
context:(MTLContext *)mtlc
renderOptions:(const RenderOptions *)renderOptions
forceUpdate:(jboolean)forceUpdate
{
if (!forceUpdate
&& [_paint isEqual:mtlc.paint]
&& [_composite isEqual:mtlc.composite]
&& (_isTexture == renderOptions->isTexture && (!renderOptions->isTexture || _interpolationMode == renderOptions->interpolation)) // interpolation is used only in texture mode
&& _isAA == renderOptions->isAA
&& _isAAShader == renderOptions->isAAShader
&& _isText == renderOptions->isText
&& _isLCD == renderOptions->isLCD
&& _srcFlags.isOpaque == renderOptions->srcFlags.isOpaque && _srcFlags.isPremultiplied == renderOptions->srcFlags.isPremultiplied)
return;
self.paint = mtlc.paint;
[_composite copyFrom:mtlc.composite];
_isTexture = renderOptions->isTexture;
_interpolationMode = renderOptions->interpolation;
_isAA = renderOptions->isAA;
_isAAShader = renderOptions->isAAShader;
_isText = renderOptions->isText;
_isLCD = renderOptions->isLCD;
_srcFlags = renderOptions->srcFlags;
if ((jint)[mtlc.composite getCompositeState] == sun_java2d_SunGraphics2D_COMP_XOR) {
[mtlc.paint setXorModePipelineState:encoder
context:mtlc
renderOptions:renderOptions
pipelineStateStorage:_pipelineStateStorage];
} else {
[mtlc.paint setPipelineState:encoder
context:mtlc
renderOptions:renderOptions
pipelineStateStorage:_pipelineStateStorage];
}
}
- (void) updateClip:(id<MTLRenderCommandEncoder>)encoder clip:(MTLClip *)clip forceUpdate:(jboolean)forceUpdate
{
if (clip.stencilMaskGenerationInProgress == JNI_TRUE) {
// don't set setScissorOrStencil when generation in progress
return;
}
if (!forceUpdate && [_clip isEqual:clip])
return;
[_clip copyFrom:clip];
[_clip setScissorOrStencil:encoder
destWidth:_destination.width
destHeight:_destination.height
device:_device];
}
- (void)updateTransform:(id <MTLRenderCommandEncoder>)encoder
transform:(MTLTransform *)transform
forceUpdate:(jboolean)forceUpdate
{
if (!forceUpdate
&& [_transform isEqual:transform])
return;
[_transform copyFrom:transform];
[_transform setVertexMatrix:encoder
destWidth:_destination.width
destHeight:_destination.height];
}
@end
@implementation EncoderManager {
MTLContext * _mtlc; // used to obtain CommandBufferWrapper and Composite/Paint/Transform
id<MTLRenderCommandEncoder> _encoder;
// 'Persistent' properties of encoder
id<MTLTexture> _destination;
id<MTLTexture> _aaDestination;
BOOL _useStencil;
// 'Mutable' states of encoder
EncoderStates * _encoderStates;
}
- (id _Nonnull)init {
self = [super init];
if (self) {
_encoder = nil;
_destination = nil;
_aaDestination = nil;
_useStencil = NO;
_encoderStates = [[EncoderStates alloc] init];
}
return self;
}
- (void)dealloc {
[_encoderStates release];
[super dealloc];
}
- (void)setContext:(MTLContex * _Nonnull)mtlc {
self->_mtlc = mtlc;
[self->_encoderStates setContext:mtlc];
}
- (id<MTLRenderCommandEncoder> _Nonnull) getRenderEncoder:(const BMTLSDOps * _Nonnull)dstOps
{
return [self getRenderEncoder:dstOps->pTexture isDstOpaque:dstOps->isOpaque];
}
- (id<MTLRenderCommandEncoder> _Nonnull)getAARenderEncoder:(const BMTLSDOps * _Nonnull)dstOps {
id<MTLTexture> dstTxt = dstOps->pTexture;
RenderOptions roptions = {JNI_FALSE, JNI_TRUE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, JNI_FALSE, JNI_FALSE, JNI_FALSE};
return [self getEncoder:dstTxt renderOptions:&roptions];
}
- (id<MTLRenderCommandEncoder> _Nonnull)getAAShaderRenderEncoder:(const BMTLSDOps * _Nonnull)dstOps
{
RenderOptions roptions = {JNI_FALSE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, JNI_FALSE, JNI_FALSE, JNI_TRUE};
return [self getEncoder:dstOps->pTexture renderOptions:&roptions];
}
- (id<MTLRenderCommandEncoder> _Nonnull)getRenderEncoder:(id<MTLTexture> _Nonnull)dest
isDstOpaque:(bool)isOpaque
{
RenderOptions roptions = {JNI_FALSE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, defaultRasterFlags, JNI_FALSE, JNI_FALSE, JNI_FALSE};
return [self getEncoder:dest renderOptions:&roptions];
}
- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(const BMTLSDOps * _Nonnull)dstOps
isSrcOpaque:(bool)isSrcOpaque
{
return [self getTextureEncoder:dstOps->pTexture
isSrcOpaque:isSrcOpaque
isDstOpaque:dstOps->isOpaque
interpolation:INTERPOLATION_NEAREST_NEIGHBOR];
}
- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
isSrcOpaque:(bool)isSrcOpaque
isDstOpaque:(bool)isDstOpaque
{
return [self getTextureEncoder:dest
isSrcOpaque:isSrcOpaque
isDstOpaque:isDstOpaque
interpolation:INTERPOLATION_NEAREST_NEIGHBOR
isAA:JNI_FALSE];
}
- (id<MTLRenderCommandEncoder> _Nonnull) getLCDEncoder:(id<MTLTexture> _Nonnull)dest
isSrcOpaque:(bool)isSrcOpaque
isDstOpaque:(bool)isDstOpaque
{
RenderOptions roptions = {JNI_TRUE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, {isSrcOpaque, JNI_TRUE }, JNI_FALSE, JNI_TRUE, JNI_FALSE};
return [self getEncoder:dest renderOptions:&roptions];
}
- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
isSrcOpaque:(bool)isSrcOpaque
isDstOpaque:(bool)isDstOpaque
interpolation:(int)interpolation
isAA:(jboolean)isAA
{
RenderOptions roptions = {JNI_TRUE, isAA, interpolation, { isSrcOpaque, JNI_TRUE }, JNI_FALSE, JNI_FALSE, JNI_FALSE};
return [self getEncoder:dest renderOptions:&roptions];
}
- (id<MTLRenderCommandEncoder> _Nonnull) getTextureEncoder:(id<MTLTexture> _Nonnull)dest
isSrcOpaque:(bool)isSrcOpaque
isDstOpaque:(bool)isDstOpaque
interpolation:(int)interpolation
{
return [self getTextureEncoder:dest isSrcOpaque:isSrcOpaque isDstOpaque:isDstOpaque interpolation:interpolation isAA:JNI_FALSE];
}
- (id<MTLRenderCommandEncoder> _Nonnull) getTextEncoder:(const BMTLSDOps * _Nonnull)dstOps
isSrcOpaque:(bool)isSrcOpaque
{
RenderOptions roptions = {JNI_TRUE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, { isSrcOpaque, JNI_TRUE },
JNI_TRUE, JNI_FALSE, JNI_FALSE};
return [self getEncoder:dstOps->pTexture renderOptions:&roptions];
}
- (id<MTLRenderCommandEncoder> _Nonnull) getEncoder:(id <MTLTexture> _Nonnull)dest
renderOptions:(const RenderOptions * _Nonnull)renderOptions
{
//
// 1. check whether it's necessary to call endEncoder
//
jboolean needEnd = JNI_FALSE;
if (_encoder != nil) {
if (_destination != dest || renderOptions->isAA != _encoderStates.aa) {
J2dTraceLn2(J2D_TRACE_VERBOSE,
"end common encoder because of dest change: %p -> %p",
_destination, dest);
needEnd = JNI_TRUE;
} else if ((_useStencil == NO) != ([_mtlc.clip isShape] == NO)) {
// 1. When mode changes RECT -> SHAPE we must recreate encoder with
// stencilAttachment (todo: consider the case when current encoder already
// has stencil)
//
// 2. When mode changes SHAPE -> RECT it seems that we can use the same
// encoder with disabled stencil test, but [encoder
// setDepthStencilState:nil] causes crash, so we have to recreate encoder
// in such case
J2dTraceLn2(J2D_TRACE_VERBOSE,
"end common encoder because toggle stencil: %d -> %d",
(int)_useStencil, (int)[_mtlc.clip isShape]);
needEnd = JNI_TRUE;
}
}
if (needEnd)
[self endEncoder];
//
// 2. recreate encoder if necessary
//
jboolean forceUpdate = JNI_FALSE;
#ifdef ALWAYS_UPDATE_ENCODER_STATES
forceUpdate = JNI_TRUE;
#endif // ALWAYS_UPDATE_ENCODER_STATES
if (_encoder == nil) {
_destination = dest;
_useStencil = [_mtlc.clip isShape] && !_mtlc.clip.stencilMaskGenerationInProgress;
forceUpdate = JNI_TRUE;
MTLCommandBufferWrapper *cbw = [_mtlc getCommandBufferWrapper];
MTLRenderPassDescriptor *rpd =
[MTLRenderPassDescriptor renderPassDescriptor];
MTLRenderPassColorAttachmentDescriptor *ca = rpd.colorAttachments[0];
ca.texture = dest;
// TODO: Find out why we cannot use
// if (_mtlc.clip.stencilMaskGenerationInProgress == YES) {
// ca.loadAction = MTLLoadActionClear;
// ca.clearColor = MTLClearColorMake(0.0f, 0.0f,0.0f, 0.0f);
// }
// here to avoid creation of clearEncoder in beginShapeClip
ca.loadAction = MTLLoadActionLoad;
ca.storeAction = MTLStoreActionStore;
if (_useStencil && !renderOptions->isAA) {
// If you enable stencil testing or stencil writing, the
// MTLRenderPassDescriptor must include a stencil attachment.
rpd.stencilAttachment.loadAction = MTLLoadActionLoad;
rpd.stencilAttachment.storeAction = MTLStoreActionStore;
rpd.stencilAttachment.texture = _mtlc.clip.stencilTextureRef;
} else if (_mtlc.clip.stencilMaskGenerationInProgress == YES) {
rpd.stencilAttachment.texture = _mtlc.clip.dstOps->pStencilTexture;
rpd.stencilAttachment.clearStencil = 0;
rpd.stencilAttachment.loadAction = _mtlc.clip.stencilMaskGenerationStarted? MTLLoadActionLoad : MTLLoadActionClear;
_mtlc.clip.stencilMaskGenerationStarted = YES;
rpd.stencilAttachment.storeAction = MTLStoreActionStore;
}
// J2dTraceLn1(J2D_TRACE_VERBOSE, "created render encoder to draw on
// tex=%p", dest);
_encoder = [[cbw getCommandBuffer] renderCommandEncoderWithDescriptor:rpd];
[_encoderStates reset:dest
isAA:renderOptions->isAA
isText:renderOptions->isText
isLCD:renderOptions->isLCD];
}
//
// 3. update encoder states
//
[_encoderStates updateEncoder:_encoder
context:_mtlc
renderOptions:renderOptions
forceUpdate:forceUpdate];
return _encoder;
}
- (id<MTLBlitCommandEncoder> _Nonnull) createBlitEncoder {
[self endEncoder];
return [[[_mtlc getCommandBufferWrapper] getCommandBuffer] blitCommandEncoder];
}
- (void) endEncoder {
if (_encoder != nil) {
[_encoder endEncoding];
_encoder = nil;
_destination = nil;
}
}
@end