// Copyright Epic Games, Inc. All Rights Reserved. #include "MuT/ASTOpImageSwizzle.h" #include "Containers/Map.h" #include "HAL/UnrealMemory.h" #include "Math/UnrealMathSSE.h" #include "Misc/AssertionMacros.h" #include "MuR/ImagePrivate.h" #include "MuR/ModelPrivate.h" #include "MuR/MutableMath.h" #include "MuR/RefCounted.h" #include "MuR/Types.h" #include "MuT/ASTOpConditional.h" #include "MuT/ASTOpImageCompose.h" #include "MuT/ASTOpImagePatch.h" #include "MuT/ASTOpImagePixelFormat.h" #include "MuT/ASTOpImageMultiLayer.h" #include "MuT/ASTOpImageLayer.h" #include "MuT/ASTOpImageLayerColor.h" #include "MuT/ASTOpImageRasterMesh.h" #include "MuT/ASTOpImageTransform.h" #include "MuT/ASTOpImageMipmap.h" #include "MuT/ASTOpImageInterpolate.h" #include "MuT/ASTOpImageSaturate.h" #include "MuT/ASTOpImagePlainColor.h" #include "MuT/ASTOpImageDisplace.h" #include "MuT/ASTOpImageInvert.h" #include "MuT/ASTOpColorSwizzle.h" #include "MuT/ASTOpSwitch.h" namespace mu { ASTOpImageSwizzle::ASTOpImageSwizzle() : Sources{ ASTChild(this),ASTChild(this),ASTChild(this),ASTChild(this) } { } ASTOpImageSwizzle::~ASTOpImageSwizzle() { // Explicit call needed to avoid recursive destruction ASTOp::RemoveChildren(); } bool ASTOpImageSwizzle::IsEqual(const ASTOp& otherUntyped) const { if (otherUntyped.GetOpType()==GetOpType()) { const ASTOpImageSwizzle* Other = static_cast(&otherUntyped); for (int32 i = 0; iSources[i] && SourceChannels[i] == Other->SourceChannels[i])) { return false; } } return Format == Other->Format; } return false; } uint64 ASTOpImageSwizzle::Hash() const { uint64 res = std::hash()(Sources[0].child().get()); hash_combine(res, std::hash()(Sources[1].child().get())); hash_combine(res, std::hash()(Sources[2].child().get())); hash_combine(res, std::hash()(Sources[3].child().get())); hash_combine(res, std::hash()(SourceChannels[0])); hash_combine(res, std::hash()(SourceChannels[1])); hash_combine(res, std::hash()(SourceChannels[2])); hash_combine(res, std::hash()(SourceChannels[3])); hash_combine(res, Format); return res; } mu::Ptr ASTOpImageSwizzle::Clone(MapChildFuncRef mapChild) const { mu::Ptr n = new ASTOpImageSwizzle(); for (int32 i = 0; i < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++i) { n->Sources[i] = mapChild(Sources[i].child()); n->SourceChannels[i] = SourceChannels[i]; } n->Format = Format; return n; } void ASTOpImageSwizzle::ForEachChild(const TFunctionRef f) { for (int32 i = 0; i < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++i) { f(Sources[i]); } } void ASTOpImageSwizzle::Link(FProgram& program, FLinkerOptions*) { // Already linked? if (!linkedAddress) { OP::ImageSwizzleArgs Args; FMemory::Memzero(Args); Args.format = Format; for (int32 i = 0; i < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++i) { if (Sources[i]) Args.sources[i] = Sources[i]->linkedAddress; Args.sourceChannels[i] = SourceChannels[i]; } linkedAddress = (OP::ADDRESS)program.OpAddress.Num(); program.OpAddress.Add((uint32)program.ByteCode.Num()); AppendCode(program.ByteCode, GetOpType()); AppendCode(program.ByteCode, Args); } } mu::Ptr ASTOpImageSwizzle::OptimiseSemantic(const FModelOptimizationOptions&, int32 Pass) const { Ptr sat; for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { Ptr candidate = Sources[c].child(); if (!candidate) { continue; } switch (candidate->GetOpType()) { // Swizzle case EOpType::IM_SWIZZLE: { if (!sat) { sat = mu::Clone(this); } const ASTOpImageSwizzle* typedCandidate = static_cast(candidate.get()); int candidateChannel = SourceChannels[c]; sat->Sources[c] = typedCandidate->Sources[candidateChannel].child(); sat->SourceChannels[c] = typedCandidate->SourceChannels[candidateChannel]; break; } // Format case EOpType::IM_PIXELFORMAT: { // We can remove the format if its source is already an uncompressed format ASTOpImagePixelFormat* typedCandidate = static_cast(candidate.get()); Ptr formatSource = typedCandidate->Source.child(); if (formatSource) { FImageDesc desc = formatSource->GetImageDesc(); if (desc.m_format != EImageFormat::None && !IsCompressedFormat(desc.m_format)) { if (!sat) { sat = mu::Clone(this); } sat->Sources[c] = formatSource; } } break; } default: break; } } return sat; } namespace { //--------------------------------------------------------------------------------------------- //! Set al the non-null sources of an image swizzle operation to the given value //--------------------------------------------------------------------------------------------- void ReplaceAllSources(Ptr& op, Ptr& value) { check(op->GetOpType() == EOpType::IM_SWIZZLE); for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { if (op->Sources[c]) { op->Sources[c] = value; } } } } mu::Ptr ASTOpImageSwizzle::OptimiseSink(const FModelOptimizationOptions& options, FOptimizeSinkContext& context) const { MUTABLE_CPUPROFILER_SCOPE(OptimiseSwizzleAST); //! Basic optimisation first Ptr at = OptimiseSemantic(options, 0); if (at) { return at; } // If all sources are the same, we can sink the instruction bool bAllChannelsAreTheSame = true; bool bAllChannelsAreTheSameType = true; Ptr channelSourceAt; for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { Ptr candidate = Sources[c].child(); if (candidate) { if (!channelSourceAt) { channelSourceAt = candidate; } else { bAllChannelsAreTheSame = bAllChannelsAreTheSame && (channelSourceAt == candidate); bAllChannelsAreTheSameType = bAllChannelsAreTheSameType && (channelSourceAt->GetOpType() == candidate->GetOpType()); } } } if (!channelSourceAt) { return at; } // If we are not changing channel order, just remove the swizzle and adjust the format. bool bSameChannelOrder = true; int32 NumChannelsInFormat = GetImageFormatData(Format).Channels; for (int32 c = 0; c < NumChannelsInFormat; ++c) { if (Sources[c] && SourceChannels[c] != c) { bSameChannelOrder = false; } } // If all channels are the same, and in the same order, and the source format is the same that we are // setting in the swizzle, then the swizzle won't do anything. if (bAllChannelsAreTheSame && bSameChannelOrder) { FImageDesc SourceDesc = channelSourceAt->GetImageDesc(); if (SourceDesc.m_format == Format) { return channelSourceAt; } } EOpType sourceType = channelSourceAt->GetOpType(); if (bAllChannelsAreTheSame) { at = context.ImageSwizzleSinker.Apply(this); } if (!at && bAllChannelsAreTheSameType) { // Maybe we can still sink the instruction in some cases // If we have RGB being the same IM_MULTILAYER, and alpha a compatible IM_MULTILAYER we can optimize with // a special multilayer blend mode. This happens often because of higher level group projector nodes. if (!at && Format == EImageFormat::RGBA_UByte && Sources[0] == Sources[1] && Sources[0] == Sources[2] && Sources[0] && Sources[0]->GetOpType() == EOpType::IM_MULTILAYER && Sources[3] && Sources[3]->GetOpType() == EOpType::IM_MULTILAYER && SourceChannels[0] == 0 && SourceChannels[1] == 1 && SourceChannels[2] == 2 && SourceChannels[3] == 0 ) { const ASTOpImageMultiLayer* ColorMultiLayer = static_cast(Sources[0].child().get()); check(ColorMultiLayer); const ASTOpImageMultiLayer* AlphaMultiLayer = static_cast(Sources[3].child().get()); check(AlphaMultiLayer); bool bIsSpecialMultiLayer = !AlphaMultiLayer->mask && ColorMultiLayer->range == AlphaMultiLayer->range; if (bIsSpecialMultiLayer) { // We can combine the 2 multilayers into the composite blend+lighten mode Ptr NewBase = mu::Clone(this); NewBase->Sources[0] = ColorMultiLayer->base.child(); NewBase->Sources[1] = ColorMultiLayer->base.child(); NewBase->Sources[2] = ColorMultiLayer->base.child(); NewBase->Sources[3] = AlphaMultiLayer->base.child(); Ptr NewBlended = mu::Clone(this); NewBlended->Sources[0] = ColorMultiLayer->blend.child(); NewBlended->Sources[1] = ColorMultiLayer->blend.child(); NewBlended->Sources[2] = ColorMultiLayer->blend.child(); NewBlended->Sources[3] = AlphaMultiLayer->blend.child(); Ptr NewMultiLayer = mu::Clone(ColorMultiLayer); NewMultiLayer->blendTypeAlpha = AlphaMultiLayer->blendType; NewMultiLayer->BlendAlphaSourceChannel = 3; NewMultiLayer->base = NewBase; NewMultiLayer->blend = NewBlended; if ( NewMultiLayer->mask.child() == AlphaMultiLayer->blend.child() && NewBlended->Format==EImageFormat::RGBA_UByte ) { // Additional optimization is possible here. NewMultiLayer->bUseMaskFromBlended = true; NewMultiLayer->mask = nullptr; } at = NewMultiLayer; } } // If we have RGB being the same IM_LAYER, and alpha a compatible IM_LAYER we can optimize with a special layer blend mode. if (!at && Format == EImageFormat::RGBA_UByte && Sources[0] == Sources[1] && (Sources[0] == Sources[2] || !Sources[2]) && Sources[0] && Sources[0]->GetOpType() == EOpType::IM_LAYER && Sources[3] && Sources[3]->GetOpType() == EOpType::IM_LAYER && SourceChannels[0] == 0 && SourceChannels[1] == 1 && (SourceChannels[2] == 2 || !Sources[2] ) && SourceChannels[3] == 0 ) { const ASTOpImageLayer* ColorLayer = static_cast(Sources[0].child().get()); check(ColorLayer); const ASTOpImageLayer* AlphaLayer = static_cast(Sources[3].child().get()); check(AlphaLayer); bool bIsSpecialMultiLayer = !AlphaLayer->mask && !ColorLayer->Flags && !AlphaLayer->Flags; if (bIsSpecialMultiLayer) { // We can combine the 2 image_layers into the composite blend+lighten mode Ptr NewBase = mu::Clone(this); NewBase->Sources[0] = ColorLayer->base.child(); NewBase->Sources[1] = ColorLayer->base.child(); NewBase->Sources[2] = Sources[2] ? ColorLayer->base.child() : nullptr; NewBase->Sources[3] = AlphaLayer->base.child(); Ptr NewBlended = mu::Clone(this); NewBlended->Sources[0] = ColorLayer->blend.child(); NewBlended->Sources[1] = ColorLayer->blend.child(); NewBlended->Sources[2] = Sources[2] ? ColorLayer->blend.child() : nullptr; NewBlended->Sources[3] = AlphaLayer->blend.child(); Ptr NewLayer = mu::Clone(ColorLayer); NewLayer->blendTypeAlpha = AlphaLayer->blendType; NewLayer->BlendAlphaSourceChannel = 3; NewLayer->base = NewBase; NewLayer->blend = NewBlended; if (NewLayer->mask.child() == AlphaLayer->blend.child() && NewBlended->Format == EImageFormat::RGBA_UByte ) { // Additional optimization is possible here. NewLayer->Flags |= OP::ImageLayerArgs::FLAGS::F_USE_MASK_FROM_BLENDED; NewLayer->mask = nullptr; } at = NewLayer; } } // If the channels are compatible switches, we can still sink the swizzle. if (!at && sourceType == EOpType::IM_SWITCH) { const ASTOpSwitch* FirstSwitch = static_cast(Sources[0].child().get()); check(FirstSwitch); bool bAreAllSwitchesCompatible = true; for (int32 c = 1; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { if (Sources[c]) { const ASTOpSwitch* Typed = static_cast(Sources[c].child().get()); check(Typed); if (!Typed->IsCompatibleWith(FirstSwitch)) { bAreAllSwitchesCompatible = false; break; } } } if (bAreAllSwitchesCompatible) { // Move the swizzle down all the paths Ptr nop = mu::Clone(channelSourceAt); if (nop->Default) { Ptr defOp = mu::Clone(this); for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { const ASTOpSwitch* ChannelSwitch = static_cast(Sources[c].child().get()); if (ChannelSwitch) { defOp->Sources[c] = ChannelSwitch->Default.child(); } } nop->Default = defOp; } for (int32 v = 0; v < nop->Cases.Num(); ++v) { if (nop->Cases[v].Branch) { Ptr branchOp = mu::Clone(this); for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { const ASTOpSwitch* ChannelSwitch = static_cast(Sources[c].child().get()); if (ChannelSwitch) { branchOp->Sources[c] = ChannelSwitch->Cases[v].Branch.child(); } } nop->Cases[v].Branch = branchOp; } } at = nop; } } // Swizzle down compatible displaces. if (!at && sourceType == EOpType::IM_DISPLACE) { const ASTOpImageDisplace* FirstDisplace = static_cast(Sources[0].child().get()); check(FirstDisplace); bool bAreAllDisplacesCompatible = true; for (int32 c = 1; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { if (Sources[c]) { const ASTOpImageDisplace* Typed = static_cast(Sources[c].child().get()); check(Typed); if (FirstDisplace->DisplacementMap != Typed->DisplacementMap) { bAreAllDisplacesCompatible = false; break; } } } if (bAreAllDisplacesCompatible) { // Move the swizzle down all the paths Ptr NewDisplace = mu::Clone(FirstDisplace); Ptr SourceOp = mu::Clone(this); for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { const ASTOpImageDisplace* ChannelDisplace = static_cast(Sources[c].child().get()); if (ChannelDisplace) { SourceOp->Sources[c] = ChannelDisplace->Source.child(); } } NewDisplace->Source = SourceOp; at = NewDisplace; } } // Swizzle down compatible raster meshes. if (!at && sourceType == EOpType::IM_RASTERMESH) { const ASTOpImageRasterMesh* FirstRasterMesh = static_cast(Sources[0].child().get()); check(FirstRasterMesh); bool bAreAllRasterMeshesCompatible = true; for (int32 c = 1; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { if (Sources[c]) { const ASTOpImageRasterMesh* Typed = static_cast(Sources[c].child().get()); check(Typed); // Compare all Args but the source image if (Typed->mesh.child() != FirstRasterMesh->mesh.child() || Typed->angleFadeProperties.child() != FirstRasterMesh->angleFadeProperties.child() || Typed->mask.child() != FirstRasterMesh->mask.child() || Typed->projector.child() != FirstRasterMesh->projector.child() || Typed->BlockId != FirstRasterMesh->BlockId || Typed->LayoutIndex != FirstRasterMesh->LayoutIndex || Typed->SizeX != FirstRasterMesh->SizeX || Typed->SizeY != FirstRasterMesh->SizeY || Typed->UncroppedSizeX != FirstRasterMesh->UncroppedSizeX || Typed->UncroppedSizeY != FirstRasterMesh->UncroppedSizeY || Typed->CropMinX != FirstRasterMesh->CropMinX || Typed->CropMinY != FirstRasterMesh->CropMinY // Also ignore the fading flags. They are dealt with below. //|| Typed->bIsRGBFadingEnabled != FirstRasterMesh->bIsRGBFadingEnabled //|| Typed->bIsAlphaFadingEnabled != FirstRasterMesh->bIsAlphaFadingEnabled ) { bAreAllRasterMeshesCompatible = false; break; } } } if (bAreAllRasterMeshesCompatible) { // Move the swizzle down all the paths Ptr NewRaster = mu::Clone(FirstRasterMesh); Ptr NewSwizzle = mu::Clone(this); for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { const ASTOpImageRasterMesh* ChannelRaster = static_cast(Sources[c].child().get()); if (ChannelRaster) { NewSwizzle->Sources[c] = ChannelRaster->image.child(); } } NewRaster->image = NewSwizzle; // If we are swapping rgb and alphas, we need to correct some flags { // We should only find these two cases if (SourceChannels[0] == 3 || SourceChannels[1] == 3 || SourceChannels[2] == 3) { NewRaster->bIsRGBFadingEnabled = NewRaster->bIsAlphaFadingEnabled; } else if (Sources[3] && SourceChannels[3] < 3) { const ASTOpImageRasterMesh* ChannelRaster = static_cast(Sources[3].child().get()); NewRaster->bIsAlphaFadingEnabled = ChannelRaster->bIsRGBFadingEnabled; } } at = NewRaster; } } // Swizzle down compatible image transforms. if (!at && sourceType == EOpType::IM_TRANSFORM) { const ASTOpImageTransform* FirstTransform = static_cast(Sources[0].child().get()); check(FirstTransform); bool bAreAllTransformsCompatible = true; for (int32 c = 1; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { if (Sources[c]) { const ASTOpImageTransform* Typed = static_cast(Sources[c].child().get()); check(Typed); // Compare all Args but the source image if (Typed->ScaleX.child() != FirstTransform->ScaleX.child() || Typed->ScaleY.child() != FirstTransform->ScaleY.child() || Typed->OffsetX.child() != FirstTransform->OffsetX.child() || Typed->OffsetY.child() != FirstTransform->OffsetY.child() || Typed->Rotation.child() != FirstTransform->Rotation.child() || Typed->SizeX != FirstTransform->SizeX || Typed->SizeY != FirstTransform->SizeY || Typed->SourceSizeX != FirstTransform->SourceSizeX || Typed->SourceSizeY != FirstTransform->SourceSizeY || Typed->AddressMode != FirstTransform->AddressMode || Typed->bKeepAspectRatio != FirstTransform->bKeepAspectRatio ) { bAreAllTransformsCompatible = false; break; } } } if (bAreAllTransformsCompatible) { // Move the swizzle down all the paths Ptr NewTransform = mu::Clone(FirstTransform); Ptr NewSwizzle = mu::Clone(this); for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { const ASTOpImageTransform* ChannelTransform = static_cast(Sources[c].child().get()); if (ChannelTransform) { NewSwizzle->Sources[c] = ChannelTransform->Base.child(); } } NewTransform->Base = NewSwizzle; at = NewTransform; } } // Swizzle down compatible resizes. //if (!at && sourceType == EOpType::IM_RESIZE) //{ // const ASTOpFixed* FirstResize = static_cast(Sources[0].child().get()); // check(FirstResize); // bool bAreAllResizesCompatible = true; // for (int32 c = 1; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) // { // if (Sources[c]) // { // const ASTOpFixed* Typed = static_cast(Sources[c].child().get()); // check(Typed); // // Compare all Args but the source image // OP::ImageResizeArgs ArgCopy = FirstResize->op.args.ImageResize; // ArgCopy.source = Typed->op.args.ImageResize.source; // if (FMemory::Memcmp(&ArgCopy, &Typed->op.args.ImageResize, sizeof(OP::ImageResizeArgs)) != 0) // { // bAreAllResizesCompatible = false; // break; // } // } // } // if (bAreAllResizesCompatible) // { // // Move the swizzle down all the paths // Ptr NewResize = mu::Clone(FirstResize); // Ptr NewSwizzle = mu::Clone(this); // for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) // { // const ASTOpFixed* ChannelResize = static_cast(Sources[c].child().get()); // if (ChannelResize) // { // NewSwizzle->Sources[c] = ChannelResize->children[ChannelResize->op.args.ImageResize.source].child(); // } // } // NewResize->SetChild(NewResize->op.args.ImageResize.source, NewSwizzle); // at = NewResize; // } //} // Swizzle down compatible pixelformats. if (!at && sourceType == EOpType::IM_PIXELFORMAT && bSameChannelOrder) { const ASTOpImagePixelFormat* FirstFormat = static_cast(Sources[0].child().get()); check(FirstFormat); bool bAreAllFormatsCompatible = true; for (int32 c = 1; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { if (Sources[c]) { const ASTOpImagePixelFormat* Typed = static_cast(Sources[c].child().get()); check(Typed); if (Typed->Source.child() != FirstFormat->Source.child()) { bAreAllFormatsCompatible = false; break; } } } if (bAreAllFormatsCompatible) { // Move the swizzle down all the paths Ptr NewFormat = mu::Clone(FirstFormat); NewFormat->Format = Format; at = NewFormat; } } // Swizzle down plaincolours. if (!at && sourceType == EOpType::IM_PLAINCOLOUR) { Ptr NewPlain = mu::Clone(channelSourceAt); Ptr NewSwizzle = new ASTOpColorSwizzle; for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { if (Sources[c]) { const ASTOpImagePlainColor* TypedPlain = static_cast(Sources[c].child().get()); NewSwizzle->Sources[c] = TypedPlain->Color.child(); } NewSwizzle->SourceChannels[c] = SourceChannels[c]; } NewPlain->Color = NewSwizzle; NewPlain->Format = Format; at = NewPlain; } } // TODO \warning: probably wrong because it doesn't check if the layer colour is doing a separated alpha operation. // Swizzle of RGB from a source + A from a layer colour // This can be optimized to apply the layer colour on-base directly to the alpha channel to skip the swizzle //if ( !at // && // Sources[0] && Sources[0]==Sources[1] && Sources[0]==Sources[2] // && // Sources[3] && Sources[3]->GetOpType()==EOpType::IM_LAYERCOLOUR // ) //{ // // Move the swizzle down all the paths // Ptr NewLayerColour = mu::Clone(Sources[3].child()); // Ptr NewSwizzle = mu::Clone(this); // NewSwizzle->Sources[3] = NewLayerColour->base.child(); // NewLayerColour->blendTypeAlpha = NewLayerColour->blendType; // NewLayerColour->BlendAlphaSourceChannel = SourceChannels[3]; // NewLayerColour->blendType = EBlendType::BT_NONE; // NewLayerColour->base = NewSwizzle; // at = NewLayerColour; //} // Swizzle of RGB from a source + A from a layer // This can be optimized to apply the layer on-base directly to the alpha channel to skip the swizzle // \TODO: wrong: the new layer colour will always use the alpha from the colour, instead of the channel that the swizzle is selecting. // \TODO: wrong: it ignores the possibility of separate alpha operation //if (!at // && // Sources[0] && Sources[0] == Sources[1] && Sources[0] == Sources[2] // && // Sources[3] && Sources[3]->GetOpType() == EOpType::IM_LAYER) //{ // // Move the swizzle down all the paths // Ptr NewLayer = mu::Clone(Sources[3].child()); // Ptr NewSwizzle = mu::Clone(this); // NewSwizzle->Sources[3] = NewLayer->base.child(); // NewLayer->blendTypeAlpha = NewLayer->blendType; // NewLayer->BlendAlphaSourceChannel = SourceChannels[3]; // NewLayer->blendType = EBlendType::BT_NONE; // NewLayer->base = NewSwizzle; // at = NewLayer; //} // Swizzle of RGB from a layer colour + A from a different source // This can be optimized to apply the layer colour on-base directly to the rgb channel to skip the swizzle if (!at && Sources[0] && (!Sources[1] || Sources[0] == Sources[1]) && (!Sources[2] || Sources[0] == Sources[2]) && Sources[0]->GetOpType() == EOpType::IM_LAYERCOLOUR && !(Sources[3]==Sources[0]) ) { // Move the swizzle down all the rgb path Ptr NewLayerColour = mu::Clone(Sources[0].child()); check(NewLayerColour); Ptr NewSwizzle = mu::Clone(this); NewSwizzle->Sources[0] = NewLayerColour->base.child(); NewSwizzle->Sources[1] = Sources[1] ? NewLayerColour->base.child() : nullptr; NewSwizzle->Sources[2] = Sources[2] ? NewLayerColour->base.child() : nullptr; NewLayerColour->blendTypeAlpha = EBlendType::BT_NONE; NewLayerColour->base = NewSwizzle; at = NewLayerColour; } // Swizzle getting an A from a saturate // The saturate doesn't affect A channel so it can be removed. if (!at) { Ptr NewSwizzle; for (int32 Channel = 0; Channel < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++Channel) { if (Sources[Channel] && SourceChannels[Channel]==3 && Sources[Channel]->GetOpType() == EOpType::IM_SATURATE) { // Remove the saturate for this channel if (!NewSwizzle) { NewSwizzle = mu::Clone(this); } const ASTOpImageSaturate* OldSaturate = static_cast(Sources[Channel].child().get()); Ptr OldSaturateBase = OldSaturate->Base.child(); NewSwizzle->Sources[Channel] = OldSaturateBase; at = NewSwizzle; } } } // Swizzle of RGB from a saturate + A from a different source // This can be optimized to apply the saturate after the swizzle, since it doesn't touch A if (!at && Sources[0] && Sources[0]->GetOpType() == EOpType::IM_SATURATE && (Sources[0] == Sources[1]) && (Sources[0] == Sources[2]) && // Actually it would be enough with all the RGB channels to be present in any order SourceChannels[0]==0 && SourceChannels[1] == 1 && SourceChannels[2] == 2 ) { // Move the swizzle down Ptr NewSaturate = mu::Clone(Sources[0].child()); check(NewSaturate); Ptr NewSwizzle = mu::Clone(this); Ptr OldSaturateBase = NewSaturate->Base.child(); NewSwizzle->Sources[0] = OldSaturateBase; NewSwizzle->Sources[1] = OldSaturateBase; NewSwizzle->Sources[2] = OldSaturateBase; // Remove the saturate from the alpha if it is there. if (Sources[3] == Sources[0] && SourceChannels[3] == 3) { NewSwizzle->Sources[3] = OldSaturateBase; } NewSaturate->Base = NewSwizzle; at = NewSaturate; } // Swizzle with the same op as identity in RGB, a Layer op in the A that has one of the operands matching // the one in the swizzle RGB, but using its A. // The Layer operation can be flagged as alpha only and moved up the swizzle, then the swizzle is identity // and can be removed, so remove it here anyway. // This is another very specific optimization case that happens with certain combination of operations. // from: //- SWIZZLE // r -> r from A // g -> g from A // b -> b from A // a -> (r or a) from LAYER // - 3 from A (on alpha only using flags) // - B // to: //- LAYER (on alpha only) // - A // - B // In addition, if the blend operation done by LAYER is commutative, see if X is 3 from I instead. if (!at && (Sources[0] == Sources[1]) && (Sources[0] == Sources[2]) && SourceChannels[0] == 0 && SourceChannels[1] == 1 && SourceChannels[2] == 2 && Sources[3] && Sources[3]->GetOpType() == EOpType::IM_LAYER ) { const ASTOpImageLayer* OldLayer = static_cast(Sources[3].child().get()); Ptr SwizzleRGBOp = Sources[0].child(); Ptr OldLayerBlendOp = OldLayer->blend.child(); { auto DiscardNeutralOps = [](Ptr Op) { bool bUpdated = true; while (bUpdated) { bUpdated = false; switch (Op->GetOpType()) { case EOpType::IM_PIXELFORMAT: { const ASTOpImagePixelFormat* Typed = static_cast(Op.get()); Op = Typed->Source.child(); bUpdated = true; break; } default: break; } } return Op; }; SwizzleRGBOp = DiscardNeutralOps(SwizzleRGBOp); OldLayerBlendOp = DiscardNeutralOps(OldLayerBlendOp); } bool bOldLayerBlendIsCompatibleWithSwizzleRGBs = OldLayerBlendOp == SwizzleRGBOp; // For now just check the case that we are observing in the working data: // A is in the blended of a multiply, and we take its alpha channel // \TODO: Implement the other cases when we find instances of them. if ( OldLayer->Flags==OP::ImageLayerArgs::FLAGS::F_BLENDED_RGB_FROM_ALPHA && bOldLayerBlendIsCompatibleWithSwizzleRGBs && OldLayer->blendType==EBlendType::BT_MULTIPLY && OldLayer->blendTypeAlpha == EBlendType::BT_NONE && SourceChannels[3]==0) { // The new base needs to have the format of the root swizzle Ptr NewBase = new ASTOpImagePixelFormat; NewBase->Source = Sources[0].child(); NewBase->Format = Format; Ptr NewLayer = mu::Clone(OldLayer); NewLayer->blend = OldLayer->base.child(); NewLayer->base = NewBase; NewLayer->blendTypeAlpha = NewLayer->blendType; NewLayer->blendType = EBlendType::BT_NONE; NewLayer->BlendAlphaSourceChannel = 0; NewLayer->Flags = 0; at = NewLayer; } } // If we have an alpha channel that has as children something that expands a single channel texture // skip the expansion,. since we know we just want one channel. // Very specific, based on observed code patterns. // \TODO: Make more general. if (!at && Sources[3] && Sources[3]->GetOpType() == EOpType::IM_LAYER ) { ASTOpImageLayer* OldLayer = static_cast(Sources[3].child().get()); // For now just check the case that we are observing in the working data: if (OldLayer->Flags == 0 && SourceChannels[3] == 0 && OldLayer->blend->GetOpType()==EOpType::IM_PIXELFORMAT ) { const ASTOpImagePixelFormat* OldFormat = static_cast(OldLayer->blend.child().get()); if (OldFormat->Source->GetOpType() == EOpType::IM_SWIZZLE && ( OldFormat->Format == EImageFormat::RGB_UByte || OldFormat->Format == EImageFormat::RGBA_UByte ) ) { const ASTOpImageSwizzle* OldChildSwizzle = static_cast(OldFormat->Source.child().get()); if (OldChildSwizzle->Format == EImageFormat::L_UByte) { Ptr NewBaseSwizzle = new ASTOpImageSwizzle; NewBaseSwizzle->Format = EImageFormat::L_UByte; NewBaseSwizzle->Sources[0] = OldLayer->base.child(); NewBaseSwizzle->SourceChannels[0] = SourceChannels[3]; Ptr NewBlendSwizzle = new ASTOpImageSwizzle; NewBlendSwizzle->Format = EImageFormat::L_UByte; NewBlendSwizzle->Sources[0] = OldLayer->blend.child(); NewBlendSwizzle->SourceChannels[0] = SourceChannels[3]; Ptr NewLayer = mu::Clone(OldLayer); NewLayer->base = NewBaseSwizzle; NewLayer->blend = NewBlendSwizzle; Ptr NewSwizzle = mu::Clone(this); NewSwizzle->Sources[3] = NewLayer; at = NewSwizzle; } } } } return at; } //! FImageDesc ASTOpImageSwizzle::GetImageDesc(bool returnBestOption, FGetImageDescContext* context) const { FImageDesc res; // Local context in case it is necessary FGetImageDescContext localContext; if (!context) { context = &localContext; } else { // Cached result? FImageDesc* PtrValue = context->m_results.Find(this); if (PtrValue) { return *PtrValue; } } int32 FirstValidSourceIndex = -1; for (int32 SourceIndex = 0; SourceIndex < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++SourceIndex) { if (Sources[SourceIndex].child()) { FirstValidSourceIndex = SourceIndex; break; } } if (FirstValidSourceIndex >= 0) { res = Sources[FirstValidSourceIndex]->GetImageDesc(returnBestOption, context); res.m_format = Format; check(res.m_format != EImageFormat::None); } // Cache the result if (context) { context->m_results.Add(this, res); } return res; } void ASTOpImageSwizzle::GetLayoutBlockSize(int* pBlockX, int* pBlockY) { if (Sources[0].child()) { // Assume the block size of the biggest mip Sources[0].child()->GetLayoutBlockSize(pBlockX, pBlockY); } } bool ASTOpImageSwizzle::IsImagePlainConstant(FVector4f& colour) const { // TODO: Maybe something could be done here. return false; } mu::Ptr ASTOpImageSwizzle::GetImageSizeExpression() const { mu::Ptr pRes; if (Sources[0].child()) { pRes = Sources[0].child()->GetImageSizeExpression(); } else { pRes = new ImageSizeExpression; } return pRes; } FSourceDataDescriptor ASTOpImageSwizzle::GetSourceDataDescriptor(FGetSourceDataDescriptorContext* Context) const { // Cache management TUniquePtr LocalContext; if (!Context) { LocalContext.Reset(new FGetSourceDataDescriptorContext); Context = LocalContext.Get(); } FSourceDataDescriptor* Found= Context->Cache.Find(this); if (Found) { return *Found; } // Not cached: calculate FSourceDataDescriptor Result; for( int32 SourceIndex=0; SourceIndexGetSourceDataDescriptor(Context); Result.CombineWith(SourceDesc); } } Context->Cache.Add(this,Result); return Result; } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- mu::Ptr Sink_ImageSwizzleAST::Apply(const ASTOpImageSwizzle* InRoot) { Root = InRoot; OldToNew.Reset(); check(Root->GetOpType() == EOpType::IM_SWIZZLE); // This sinker only works assuming all swizzle channels come from the same image operation. bool bAllChannelsAreTheSame = true; Ptr Source; for (int c = 0; c < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++c) { Ptr Candidate = Root->Sources[c].child(); if (Candidate) { if (!Source) { Source = Candidate; } else { bAllChannelsAreTheSame = bAllChannelsAreTheSame && (Source == Candidate); } } } if (!bAllChannelsAreTheSame || !Source) { return nullptr; } InitialSource = Source; mu::Ptr NewSource = Visit(InitialSource, Root); Root = nullptr; // If there is any change, it is the new root. if (NewSource != InitialSource) { return NewSource; } return nullptr; } //--------------------------------------------------------------------------------------------- mu::Ptr Sink_ImageSwizzleAST::Visit(mu::Ptr at, const ASTOpImageSwizzle* CurrentSwizzleOp) { if (!at) return nullptr; // Already visited? const Ptr* Cached = OldToNew.Find({ at,CurrentSwizzleOp }); if (Cached) { return *Cached; } mu::Ptr newAt = at; switch (at->GetOpType()) { case EOpType::IM_CONDITIONAL: { // We move the op down the two paths auto newOp = mu::Clone(at); newOp->yes = Visit(newOp->yes.child(), CurrentSwizzleOp); newOp->no = Visit(newOp->no.child(), CurrentSwizzleOp); newAt = newOp; break; } case EOpType::IM_SWITCH: { // We move the op down all the paths Ptr newOp = mu::Clone(at); newOp->Default = Visit(newOp->Default.child(), CurrentSwizzleOp); for (ASTOpSwitch::FCase& c : newOp->Cases) { c.Branch = Visit(c.Branch.child(), CurrentSwizzleOp); } newAt = newOp; break; } case EOpType::IM_COMPOSE: { Ptr newOp = mu::Clone(at); newOp->Base = Visit(newOp->Base.child(), CurrentSwizzleOp); newOp->BlockImage = Visit(newOp->BlockImage.child(), CurrentSwizzleOp); newAt = newOp; break; } case EOpType::IM_PATCH: { Ptr newOp = mu::Clone(at); newOp->base = Visit(newOp->base.child(), CurrentSwizzleOp); newOp->patch = Visit(newOp->patch.child(), CurrentSwizzleOp); newAt = newOp; break; } case EOpType::IM_MIPMAP: { Ptr newOp = mu::Clone(at); newOp->Source = Visit(newOp->Source.child(), CurrentSwizzleOp); newAt = newOp; break; } case EOpType::IM_INTERPOLATE: { Ptr NewOp = mu::Clone(at); for (int32 v = 0; v < MUTABLE_OP_MAX_INTERPOLATE_COUNT; ++v) { NewOp->Targets[v] = Visit(NewOp->Targets[v].child(), CurrentSwizzleOp);; } newAt = NewOp; break; } case EOpType::IM_LAYER: { Ptr nop = mu::Clone(at); nop->base = Visit(nop->base.child(), CurrentSwizzleOp); nop->blend = Visit(nop->blend.child(), CurrentSwizzleOp); newAt = nop; break; } case EOpType::IM_LAYERCOLOUR: { Ptr nop = mu::Clone(at); nop->base = Visit(nop->base.child(), CurrentSwizzleOp); // We need to swizzle the colour too Ptr NewColorOp = new ASTOpColorSwizzle; for (int i = 0; i < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++i) { NewColorOp->Sources[i] = nop->color.child(); NewColorOp->SourceChannels[i] = CurrentSwizzleOp->SourceChannels[i]; } nop->color = NewColorOp; newAt = nop; break; } case EOpType::IM_DISPLACE: { Ptr NewOp = mu::Clone(at); Ptr Child = NewOp->Source.child(); Ptr NewSource = Visit(Child, CurrentSwizzleOp); NewOp->Source = NewSource; newAt = NewOp; break; } case EOpType::IM_INVERT: { Ptr NewOp = mu::Clone(at); Ptr Child = NewOp->Base.child(); Ptr NewSource = Visit(Child, CurrentSwizzleOp); NewOp->Base = NewSource; newAt = NewOp; break; } case EOpType::IM_RASTERMESH: { Ptr NewOp = mu::Clone(at); NewOp->image = Visit(NewOp->image.child(), CurrentSwizzleOp); // If we are swapping rgb and alphas, we need to correct some flags { // We should only find these two cases if (CurrentSwizzleOp->SourceChannels[0] == 3 || CurrentSwizzleOp->SourceChannels[1] == 3 || CurrentSwizzleOp->SourceChannels[2] == 3) { NewOp->bIsRGBFadingEnabled = NewOp->bIsAlphaFadingEnabled; } else if (CurrentSwizzleOp->Sources[3] && CurrentSwizzleOp->SourceChannels[3] < 3) { NewOp->bIsAlphaFadingEnabled = NewOp->bIsRGBFadingEnabled; } } newAt = NewOp; break; } case EOpType::IM_TRANSFORM: { Ptr nop = mu::Clone(at); nop->Base = Visit(nop->Base.child(), CurrentSwizzleOp); newAt = nop; break; } case EOpType::IM_PIXELFORMAT: { // If we are not changing channel order, just remove the swizzle and adjust the format. bool bSameChannelOrder = true; int32 NumChannelsInFormat = GetImageFormatData(CurrentSwizzleOp->Format).Channels; for (int32 c = 0; c < NumChannelsInFormat; ++c) { if (CurrentSwizzleOp->Sources[c] && CurrentSwizzleOp->SourceChannels[c] != c) { bSameChannelOrder = false; } } if (bSameChannelOrder) { Ptr NewOp = mu::Clone(at); NewOp->Format = CurrentSwizzleOp->Format; newAt = NewOp; } break; } case EOpType::IM_BLANKLAYOUT: { // We can remove the swizzle entirely. // It is not 100% equivalent, because blank layouts are initialized with 0,0,0,1 so the result could be // different, but those pixels shouldn't be used anyway. newAt = mu::Clone(at); break; } default: break; } // end on tree branch, replace with format if (at == newAt && at != InitialSource) { mu::Ptr NewOp = mu::Clone(CurrentSwizzleOp); check(NewOp->GetOpType() == EOpType::IM_SWIZZLE); ReplaceAllSources(NewOp, at); newAt = NewOp; } OldToNew.Add({ at, CurrentSwizzleOp }, newAt); return newAt; } }