[WIP] MetalEngine

This commit is contained in:
Ali 2023-11-11 00:43:25 +04:00
parent 5976d495b0
commit 88a0fd7a81
10 changed files with 162 additions and 43 deletions

View File

@ -71,6 +71,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case keepChatNavigationStack(PresentationTheme, Bool)
case skipReadHistory(PresentationTheme, Bool)
case unidirectionalSwipeToReply(Bool)
case dustEffect(Bool)
case crashOnSlowQueries(PresentationTheme, Bool)
case crashOnMemoryPressure(PresentationTheme, Bool)
case clearTips(PresentationTheme)
@ -119,7 +120,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logs.rawValue
case .logToFile, .logToConsole, .redactSensitiveData:
return DebugControllerSection.logging.rawValue
case .keepChatNavigationStack, .skipReadHistory, .unidirectionalSwipeToReply, .crashOnSlowQueries, .crashOnMemoryPressure:
case .keepChatNavigationStack, .skipReadHistory, .unidirectionalSwipeToReply, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases:
return DebugControllerSection.experiments.rawValue
@ -168,70 +169,72 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 15
case .unidirectionalSwipeToReply:
return 16
case .crashOnSlowQueries:
case .dustEffect:
return 17
case .crashOnMemoryPressure:
case .crashOnSlowQueries:
return 18
case .clearTips:
case .crashOnMemoryPressure:
return 19
case .resetNotifications:
case .clearTips:
return 20
case .crash:
case .resetNotifications:
return 21
case .resetData:
case .crash:
return 22
case .resetDatabase:
case .resetData:
return 23
case .resetDatabaseAndCache:
case .resetDatabase:
return 24
case .resetHoles:
case .resetDatabaseAndCache:
return 25
case .reindexUnread:
case .resetHoles:
return 26
case .resetCacheIndex:
case .reindexUnread:
return 27
case .reindexCache:
case .resetCacheIndex:
return 28
case .resetBiometricsData:
case .reindexCache:
return 29
case .resetWebViewCache:
case .resetBiometricsData:
return 30
case .optimizeDatabase:
case .resetWebViewCache:
return 31
case .photoPreview:
case .optimizeDatabase:
return 32
case .knockoutWallpaper:
case .photoPreview:
return 33
case .experimentalCompatibility:
case .knockoutWallpaper:
return 34
case .enableDebugDataDisplay:
case .experimentalCompatibility:
return 35
case .acceleratedStickers:
case .enableDebugDataDisplay:
return 36
case .inlineForums:
case .acceleratedStickers:
return 37
case .localTranscription:
case .inlineForums:
return 38
case .enableReactionOverrides:
case .localTranscription:
return 39
case .restorePurchases:
case .enableReactionOverrides:
return 40
case .logTranslationRecognition:
case .restorePurchases:
return 41
case .resetTranslationStates:
case .logTranslationRecognition:
return 42
case .storiesExperiment:
case .resetTranslationStates:
return 43
case .storiesJpegExperiment:
case .storiesExperiment:
return 44
case .playlistPlayback:
case .storiesJpegExperiment:
return 45
case .enableQuickReactionSwitch:
case .playlistPlayback:
return 46
case .voiceConference:
case .enableQuickReactionSwitch:
return 47
case .voiceConference:
return 48
case let .preferredVideoCodec(index, _, _, _):
return 48 + index
return 49 + index
case .disableVideoAspectScaling:
return 100
case .enableNetworkFramework:
@ -939,6 +942,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return settings
}).start()
})
case let .dustEffect(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Dust Effect", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings
settings.dustEffect = value
return settings
}).start()
})
case let .crashOnSlowQueries(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
@ -1387,6 +1398,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
#endif
entries.append(.unidirectionalSwipeToReply(experimentalSettings.unidirectionalSwipeToReply))
entries.append(.dustEffect(experimentalSettings.dustEffect))
}
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
entries.append(.crashOnMemoryPressure(presentationData.theme, experimentalSettings.crashOnMemoryPressure))

View File

@ -4669,6 +4669,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
}
}
public func forEachRemovedItemNode(_ f: (ASDisplayNode) -> Void) {
for itemNode in self.itemNodes {
if itemNode.index == nil {
f(itemNode)
}
}
}
public func enumerateItemNodes(_ f: (ASDisplayNode) -> Bool) {
for itemNode in self.itemNodes {

View File

@ -411,6 +411,7 @@ swift_library(
"//submodules/TelegramUI/Components/ContextMenuScreen",
"//submodules/TelegramUI/Components/PeerAllowedReactionsScreen",
"//submodules/MetalEngine",
"//submodules/TelegramUI/Components/DustEffect",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],

View File

@ -5382,4 +5382,22 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
override public func contentFrame() -> CGRect {
return self.backgroundNode.frame
}
override public func makeContentSnapshot() -> (UIImage, CGRect)? {
UIGraphicsBeginImageContextWithOptions(self.backgroundNode.view.bounds.size, false, 0.0)
let context = UIGraphicsGetCurrentContext()!
context.translateBy(x: -self.backgroundNode.frame.minX, y: -self.insets.top - self.backgroundNode.frame.minY)
self.view.drawHierarchy(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.view.bounds.size), afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let image else {
return nil
}
return (image, self.backgroundNode.frame)
}
}

View File

@ -716,6 +716,10 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
return nil
}
open func makeContentSnapshot() -> (UIImage, CGRect)? {
return nil
}
open func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
return nil
}

View File

@ -124,6 +124,6 @@ fragment half4 dustEffectFragment(
) {
constexpr sampler sampler(coord::normalized, address::clamp_to_edge, filter::linear);
half3 color = inTexture.sample(sampler, float2(in.uv.x, 1.0 - in.uv.y)).rgb;
return half4(color * in.alpha, in.alpha);
half4 color = inTexture.sample(sampler, float2(in.uv.x, 1.0 - in.uv.y));
return color * in.alpha;
}

View File

@ -95,9 +95,10 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
}
private var updateLink: SharedDisplayLinkDriver.Link?
private var items: [Item] = []
public var becameEmpty: (() -> Void)?
override public init() {
super.init()
@ -127,14 +128,20 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
}
private func updateItems() {
var didRemoveItems = false
for i in (0 ..< self.items.count).reversed() {
self.items[i].phase += 1.0 / 60.0
self.items[i].phase += (1.0 / 60.0) / Float(UIView.animationDurationFactor())
if self.items[i].phase >= 4.0 {
self.items.remove(at: i)
didRemoveItems = true
}
}
self.updateNeedsAnimation()
if didRemoveItems && self.items.isEmpty {
self.becameEmpty?()
}
}
private func updateNeedsAnimation() {
@ -222,7 +229,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
computeEncoder.setBytes(&particleCount, length: 4 * 2, index: 1)
var phase = item.phase
computeEncoder.setBytes(&phase, length: 4, index: 2)
var timeStep: Float = 1.0 / 60.0
var timeStep: Float = (1.0 / 60.0) / Float(UIView.animationDurationFactor())
computeEncoder.setBytes(&timeStep, length: 4, index: 3)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
}

View File

@ -17117,11 +17117,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}
contextItems.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { [weak self] _, f in
contextItems.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, f in
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
f(.dismissWithoutContent)
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
c.dismiss(completion: { [weak strongSelf] in
guard let strongSelf else {
return
}
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
})
} else {
f(.dismissWithoutContent)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
}
}
})))
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak self, weak actionSheet] in

View File

@ -31,6 +31,7 @@ import ChatMessageItemImpl
import ChatMessageItemView
import ChatMessageTransitionNode
import ChatControllerInteraction
import DustEffect
struct ChatTopVisibleMessageRange: Equatable {
var lowerBound: MessageIndex
@ -699,6 +700,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private var toLang: String?
private var dustEffectLayer: DustEffectLayer?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?, source: ChatHistoryListSource = .default, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl? = { nil }) {
var tagMask = tagMask
if case .pinnedMessages = subject {
@ -3195,6 +3198,50 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
}
if let currentDeleteAnimationCorrelationIds = strongSelf.currentDeleteAnimationCorrelationIds {
var foundItemNodes: [ChatMessageItemView] = []
strongSelf.forEachRemovedItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
for (message, _) in item.content {
if currentDeleteAnimationCorrelationIds.contains(message.id) {
foundItemNodes.append(itemNode)
}
}
}
}
if !foundItemNodes.isEmpty {
strongSelf.currentDeleteAnimationCorrelationIds = nil
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
if strongSelf.dustEffectLayer == nil {
let dustEffectLayer = DustEffectLayer()
dustEffectLayer.position = strongSelf.bounds.center
dustEffectLayer.bounds = CGRect(origin: CGPoint(), size: strongSelf.bounds.size)
strongSelf.dustEffectLayer = dustEffectLayer
dustEffectLayer.zPosition = 10.0
dustEffectLayer.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
strongSelf.layer.addSublayer(dustEffectLayer)
dustEffectLayer.becameEmpty = { [weak strongSelf] in
guard let strongSelf else {
return
}
strongSelf.dustEffectLayer?.removeFromSuperlayer()
strongSelf.dustEffectLayer = nil
}
}
if let dustEffectLayer = strongSelf.dustEffectLayer {
for itemNode in foundItemNodes {
guard let (image, subFrame) = itemNode.makeContentSnapshot() else {
continue
}
let itemFrame = itemNode.layer.convert(subFrame, to: dustEffectLayer)
dustEffectLayer.addItem(frame: itemFrame, image: image)
itemNode.isHidden = true
}
}
}
}
}
if !newIncomingReactions.isEmpty {
let messageIds = Array(newIncomingReactions.keys)
@ -3872,6 +3919,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
func setCurrentSendAnimationCorrelationIds(_ value: Set<Int64>?) {
self.currentSendAnimationCorrelationIds = value
}
private var currentDeleteAnimationCorrelationIds: Set<MessageId>?
func setCurrentDeleteAnimationCorrelationIds(_ value: Set<MessageId>?) {
self.currentDeleteAnimationCorrelationIds = value
}
var animationCorrelationMessagesFound: (([Int64: ChatMessageItemView]) -> Void)?

View File

@ -54,6 +54,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var storiesJpegExperiment: Bool
public var crashOnMemoryPressure: Bool
public var unidirectionalSwipeToReply: Bool
public var dustEffect: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -85,7 +86,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
storiesExperiment: false,
storiesJpegExperiment: false,
crashOnMemoryPressure: false,
unidirectionalSwipeToReply: false
unidirectionalSwipeToReply: false,
dustEffect: false
)
}
@ -118,7 +120,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
storiesExperiment: Bool,
storiesJpegExperiment: Bool,
crashOnMemoryPressure: Bool,
unidirectionalSwipeToReply: Bool
unidirectionalSwipeToReply: Bool,
dustEffect: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -149,6 +152,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.storiesJpegExperiment = storiesJpegExperiment
self.crashOnMemoryPressure = crashOnMemoryPressure
self.unidirectionalSwipeToReply = unidirectionalSwipeToReply
self.dustEffect = dustEffect
}
public init(from decoder: Decoder) throws {
@ -183,6 +187,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.storiesJpegExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesJpegExperiment") ?? false
self.crashOnMemoryPressure = try container.decodeIfPresent(Bool.self, forKey: "crashOnMemoryPressure") ?? false
self.unidirectionalSwipeToReply = try container.decodeIfPresent(Bool.self, forKey: "unidirectionalSwipeToReply") ?? false
self.dustEffect = try container.decodeIfPresent(Bool.self, forKey: "dustEffect") ?? false
}
public func encode(to encoder: Encoder) throws {
@ -217,6 +222,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode(self.storiesJpegExperiment, forKey: "storiesJpegExperiment")
try container.encode(self.crashOnMemoryPressure, forKey: "crashOnMemoryPressure")
try container.encode(self.unidirectionalSwipeToReply, forKey: "unidirectionalSwipeToReply")
try container.encode(self.dustEffect, forKey: "dustEffect")
}
}