diff --git a/Tests/CallUITest/Resources/test.png b/Tests/CallUITest/Resources/test.png new file mode 100644 index 0000000000..59ef68aabd Binary files /dev/null and b/Tests/CallUITest/Resources/test.png differ diff --git a/Tests/CallUITest/Resources/test2.mp4 b/Tests/CallUITest/Resources/test2.mp4 new file mode 100644 index 0000000000..c0327e91e3 Binary files /dev/null and b/Tests/CallUITest/Resources/test2.mp4 differ diff --git a/submodules/MetalEngine/BUILD b/submodules/MetalEngine/BUILD new file mode 100644 index 0000000000..2460b6126b --- /dev/null +++ b/submodules/MetalEngine/BUILD @@ -0,0 +1,63 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +load( + "@build_bazel_rules_apple//apple:resources.bzl", + "apple_resource_bundle", + "apple_resource_group", +) +load("//build-system/bazel-utils:plist_fragment.bzl", + "plist_fragment", +) + +filegroup( + name = "MetalSources", + srcs = glob([ + "Metal/**/*.metal", + ]), + visibility = ["//visibility:public"], +) + +plist_fragment( + name = "MetalEngineMetalSourcesBundleInfoPlist", + extension = "plist", + template = + """ + CFBundleIdentifier + org.telegram.MetalEngineMetalSources + CFBundleDevelopmentRegion + en + CFBundleName + MetalEngine + """ +) + +apple_resource_bundle( + name = "MetalEngineMetalSourcesBundle", + infoplists = [ + ":MetalEngineMetalSourcesBundleInfoPlist", + ], + resources = [ + ":MetalSources", + ], +) + +swift_library( + name = "MetalEngine", + module_name = "MetalEngine", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + data = [ + ":MetalEngineMetalSourcesBundle", + ], + deps = [ + "//submodules/Display", + "//submodules/Utils/ShelfPack", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/MetalEngine/Metal/MetalEngineShaders.metal b/submodules/MetalEngine/Metal/MetalEngineShaders.metal new file mode 100644 index 0000000000..0e751f4fa4 --- /dev/null +++ b/submodules/MetalEngine/Metal/MetalEngineShaders.metal @@ -0,0 +1,11 @@ +#include + +using namespace metal; + +vertex float4 clearVertex(const device float2* vertexArray [[ buffer(0) ]], unsigned int vid [[ vertex_id ]]) { + return float4(vertexArray[vid], 0.0, 1.0); +} + +fragment half4 clearFragment(const device float4 &color [[ buffer(0) ]]) { + return half4(color); +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Utils/AlignUp.swift b/submodules/MetalEngine/Sources/AlignUp.swift similarity index 100% rename from submodules/TelegramUI/Components/Calls/CallScreen/Sources/Utils/AlignUp.swift rename to submodules/MetalEngine/Sources/AlignUp.swift diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Rendering/MetalLayerContext.swift b/submodules/MetalEngine/Sources/MetalEngine.swift similarity index 94% rename from submodules/TelegramUI/Components/Calls/CallScreen/Sources/Rendering/MetalLayerContext.swift rename to submodules/MetalEngine/Sources/MetalEngine.swift index 03b45b95a9..2b8f1348e2 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Rendering/MetalLayerContext.swift +++ b/submodules/MetalEngine/Sources/MetalEngine.swift @@ -2,15 +2,9 @@ import Foundation import Metal import UIKit import IOSurface +import Display import ShelfPack -private func alignUp(size: Int, align: Int) -> Int { - precondition(((align - 1) & align) == 0, "Align must be a power of two") - - let alignmentMask = align - 1 - return (size + alignmentMask) & ~alignmentMask -} - public final class Placeholder { var contents: Resolved? } @@ -110,16 +104,16 @@ public struct RenderLayerPlacement: Equatable { public protocol RenderToLayerState: AnyObject { var pipelineState: MTLRenderPipelineState { get } - init?(device: MTLDevice, library: MTLLibrary) + init?(device: MTLDevice) } public protocol ComputeState: AnyObject { - init?(device: MTLDevice, library: MTLLibrary) + init?(device: MTLDevice) } -open class MetalSubjectLayer: SimpleLayer { +open class MetalEngineSubjectLayer: SimpleLayer { fileprivate var internalId: Int = -1 - fileprivate var surfaceAllocation: MetalContext.SurfaceAllocation? + fileprivate var surfaceAllocation: MetalEngine.SurfaceAllocation? public override init() { super.init() @@ -136,7 +130,7 @@ open class MetalSubjectLayer: SimpleLayer { } override public func setNeedsDisplay() { - if let subject = self as? MetalSubject { + if let subject = self as? MetalEngineSubject { subject.setNeedsUpdate() } } @@ -171,7 +165,7 @@ public final class PooledTexture { } } - public func get(context: MetalSubjectContext) -> TexturePlaceholder? { + public func get(context: MetalEngineSubjectContext) -> TexturePlaceholder? { #if DEBUG if context.freeResourcesOnCompletion.contains(where: { $0 === self }) { assertionFailure("Trying to get PooledTexture more than once per update cycle") @@ -193,7 +187,7 @@ public final class PooledTexture { } } -public final class MetalSubjectContext { +public final class MetalEngineSubjectContext { fileprivate final class ComputeOperation { let commands: (MTLCommandBuffer) -> Void @@ -205,13 +199,13 @@ public final class MetalSubjectContext { fileprivate final class RenderToLayerOperation { let spec: RenderLayerSpec let state: RenderToLayerState - weak var layer: MetalSubjectLayer? + weak var layer: MetalEngineSubjectLayer? let commands: (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void init( spec: RenderLayerSpec, state: RenderToLayerState, - layer: MetalSubjectLayer, + layer: MetalEngineSubjectLayer, commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void ) { self.spec = spec @@ -222,13 +216,13 @@ public final class MetalSubjectContext { } private let device: MTLDevice - private let impl: MetalContext.Impl + private let impl: MetalEngine.Impl fileprivate var computeOperations: [ComputeOperation] = [] fileprivate var renderToLayerOperationsGroupedByState: [ObjectIdentifier: [RenderToLayerOperation]] = [:] fileprivate var freeResourcesOnCompletion: [PooledTexture.Texture] = [] - fileprivate init(device: MTLDevice, impl: MetalContext.Impl) { + fileprivate init(device: MTLDevice, impl: MetalEngine.Impl) { self.device = device self.impl = impl } @@ -236,7 +230,7 @@ public final class MetalSubjectContext { public func renderToLayer( spec: RenderLayerSpec, state: RenderToLayerStateType.Type, - layer: MetalSubjectLayer, + layer: MetalEngineSubjectLayer, inputs: repeat Placeholder, commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement, repeat each Resolved) -> Void ) { @@ -245,7 +239,7 @@ public final class MetalSubjectContext { if let current = self.impl.renderStates[stateTypeId] as? RenderToLayerStateType { resolvedState = current } else { - guard let value = RenderToLayerStateType(device: self.device, library: self.impl.library) else { + guard let value = RenderToLayerStateType(device: self.device) else { assertionFailure("Could not initialize render state \(state)") return } @@ -278,7 +272,7 @@ public final class MetalSubjectContext { public func renderToLayer( spec: RenderLayerSpec, state: RenderToLayerStateType.Type, - layer: MetalSubjectLayer, + layer: MetalEngineSubjectLayer, commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void ) { self.renderToLayer(spec: spec, state: state, layer: layer, inputs: noInputPlaceholder, commands: { encoder, placement, _ in @@ -296,7 +290,7 @@ public final class MetalSubjectContext { if let current = self.impl.computeStates[stateTypeId] as? ComputeStateType { resolvedState = current } else { - guard let value = ComputeStateType(device: self.device, library: self.impl.library) else { + guard let value = ComputeStateType(device: self.device) else { assertionFailure("Could not initialize compute state \(state)") return Placeholder() } @@ -338,24 +332,24 @@ public final class MetalSubjectContext { } } -public final class MetalSubjectInternalData { +public final class MetalEngineSubjectInternalData { var internalId: Int = -1 - var renderSurfaceAllocation: MetalContext.SurfaceAllocation? + var renderSurfaceAllocation: MetalEngine.SurfaceAllocation? init() { } } -public protocol MetalSubject: AnyObject { - var internalData: MetalSubjectInternalData? { get set } +public protocol MetalEngineSubject: AnyObject { + var internalData: MetalEngineSubjectInternalData? { get set } func setNeedsUpdate() - func update(context: MetalSubjectContext) + func update(context: MetalEngineSubjectContext) } -public extension MetalSubject { +public extension MetalEngineSubject { func setNeedsUpdate() { - MetalContext.shared.impl.addSubjectNeedsUpdate(subject: self) + MetalEngine.shared.impl.addSubjectNeedsUpdate(subject: self) } } @@ -370,7 +364,7 @@ private final class MetalEventLayer: CAMetalLayer { } } -public final class MetalContext { +public final class MetalEngine { struct SurfaceAllocation { struct Phase { let subRect: CGRect @@ -509,9 +503,9 @@ public final class MetalContext { } private final class SubjectReference { - weak var subject: MetalSubject? + weak var subject: MetalEngineSubject? - init(subject: MetalSubject) { + init(subject: MetalEngineSubject) { self.subject = subject } } @@ -548,7 +542,7 @@ public final class MetalContext { init?(device: MTLDevice) { let mainBundle = Bundle(for: Impl.self) - guard let path = mainBundle.path(forResource: "MetalSourcesBundle", ofType: "bundle") else { + guard let path = mainBundle.path(forResource: "MetalEngineMetalSourcesBundle", ofType: "bundle") else { return nil } guard let bundle = Bundle(path: path) else { @@ -624,15 +618,15 @@ public final class MetalContext { let surfaceId = self.nextSurfaceId self.nextSurfaceId += 1 - let surfaceWidth = max(1024, alignUp(size: Int(minSize.width), align: 64)) - let surfaceHeight = max(512, alignUp(size: Int(minSize.height), align: 64)) + let surfaceWidth = max(1024, alignUp(Int(minSize.width), alignment: 64)) + let surfaceHeight = max(512, alignUp(Int(minSize.height), alignment: 64)) let surface = Surface(id: surfaceId, device: self.device, width: surfaceWidth, height: surfaceHeight) self.surfaces[surfaceId] = surface return surface } - private func refreshLayerAllocation(layer: MetalSubjectLayer, renderSpec: RenderLayerSpec) { + private func refreshLayerAllocation(layer: MetalEngineSubjectLayer, renderSpec: RenderLayerSpec) { var previousSurfaceId: Int? var updatedSurfaceId: Int? @@ -689,12 +683,12 @@ public final class MetalContext { } } - func addSubjectNeedsUpdate(subject: MetalSubject) { - let internalData: MetalSubjectInternalData + func addSubjectNeedsUpdate(subject: MetalEngineSubject) { + let internalData: MetalEngineSubjectInternalData if let current = subject.internalData { internalData = current } else { - internalData = MetalSubjectInternalData() + internalData = MetalEngineSubjectInternalData() subject.internalData = internalData } @@ -751,7 +745,7 @@ public final class MetalContext { return } - let subjectContext = MetalSubjectContext(device: device, impl: self) + let subjectContext = MetalEngineSubjectContext(device: device, impl: self) for subjectReference in self.updatedSubjects { guard let subject = subjectReference.subject else { @@ -899,7 +893,7 @@ public final class MetalContext { } } - public static let shared = MetalContext() + public static let shared = MetalEngine() fileprivate let impl: Impl diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index dbd5bc8794..6179f39b69 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -105,6 +105,7 @@ swift_library( "//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer", "//submodules/PeerInfoUI/CreateExternalMediaStreamScreen:CreateExternalMediaStreamScreen", "//submodules/PhoneNumberFormat:PhoneNumberFormat", + "//submodules/TelegramUI/Components/Calls/CallScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index 5845fc5d36..2e0db9a12c 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -135,11 +135,11 @@ public final class CallController: ViewController { } override public func loadDisplayNode() { - if self.call.isVideoPossible { - self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call) - } else { - self.displayNode = LegacyCallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call) - } + #if DEBUG + self.displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call) + #else + self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call) + #endif self.displayNodeDidLoad() self.controllerNode.toggleMute = { [weak self] in diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift new file mode 100644 index 0000000000..0749b43341 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -0,0 +1,91 @@ +import Foundation +import AsyncDisplayKit +import Display +import TelegramCore +import Postbox +import TelegramAudio +import AccountContext +import TelegramPresentationData +import SwiftSignalKit +import CallScreen + +final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeProtocol { + private let sharedContext: SharedAccountContext + private let account: Account + private let presentationData: PresentationData + private let statusBar: StatusBar + private let call: PresentationCall + + private let callScreen: PrivateCallScreen + + var isMuted: Bool = false + + var toggleMute: (() -> Void)? + var setCurrentAudioOutput: ((AudioSessionOutput) -> Void)? + var beginAudioOuputSelection: ((Bool) -> Void)? + var acceptCall: (() -> Void)? + var endCall: (() -> Void)? + var back: (() -> Void)? + var presentCallRating: ((CallId, Bool) -> Void)? + var present: ((ViewController) -> Void)? + var callEnded: ((Bool) -> Void)? + var dismissedInteractively: (() -> Void)? + var dismissAllTooltips: (() -> Void)? + + init( + sharedContext: SharedAccountContext, + account: Account, + presentationData: PresentationData, + statusBar: StatusBar, + debugInfo: Signal<(String, String), NoError>, + shouldStayHiddenUntilConnection: Bool = false, + easyDebugAccess: Bool, + call: PresentationCall + ) { + self.sharedContext = sharedContext + self.account = account + self.presentationData = presentationData + self.statusBar = statusBar + self.call = call + + self.callScreen = PrivateCallScreen() + + super.init() + + self.view.addSubview(self.callScreen) + } + + func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) { + + } + + func updateCallState(_ callState: PresentationCallState) { + if case let .terminated(id, _, reportRating) = callState.state, let callId = id { + if reportRating { + self.presentCallRating?(callId, self.call.isVideo) + } + self.callEnded?(reportRating) + } + } + + func updatePeer(accountPeer: Peer, peer: Peer, hasOther: Bool) { + + } + + func animateIn() { + + } + + func animateOut(completion: @escaping () -> Void) { + + } + + func expandFromPipIfPossible() { + + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + transition.updateFrame(view: self.callScreen, frame: CGRect(origin: CGPoint(), size: layout.size)) + self.callScreen.update(size: layout.size, insets: layout.insets(options: [.statusBar])) + } +} diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index bf1fae9910..e58329bdd9 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -410,6 +410,7 @@ swift_library( "//submodules/TelegramUI/Components/Settings/PeerNameColorScreen", "//submodules/TelegramUI/Components/ContextMenuScreen", "//submodules/TelegramUI/Components/PeerAllowedReactionsScreen", + "//submodules/MetalEngine", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/BUILD b/submodules/TelegramUI/Components/Calls/CallScreen/BUILD index 059d137551..7fa37262e1 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/BUILD +++ b/submodules/TelegramUI/Components/Calls/CallScreen/BUILD @@ -10,7 +10,7 @@ load("//build-system/bazel-utils:plist_fragment.bzl", ) filegroup( - name = "MetalSources", + name = "CallScreenMetalSources", srcs = glob([ "Metal/**/*.metal", ]), @@ -18,7 +18,7 @@ filegroup( ) plist_fragment( - name = "MetalSourcesBundleInfoPlist", + name = "CallScreenMetalSourcesBundleInfoPlist", extension = "plist", template = """ @@ -27,20 +27,26 @@ plist_fragment( CFBundleDevelopmentRegion en CFBundleName - AnimationCompression + CallScreen """ ) apple_resource_bundle( - name = "MetalSourcesBundle", + name = "CallScreenMetalSourcesBundle", infoplists = [ - ":MetalSourcesBundleInfoPlist", + ":CallScreenMetalSourcesBundleInfoPlist", ], resources = [ - ":MetalSources", + ":CallScreenMetalSources", ], ) +filegroup( + name = "Assets", + srcs = glob(["CallScreenAssets.xcassets/**"]), + visibility = ["//visibility:public"], +) + swift_library( name = "CallScreen", module_name = "CallScreen", @@ -51,13 +57,14 @@ swift_library( "-warnings-as-errors", ], data = [ - ":MetalSourcesBundle", + ":CallScreenMetalSourcesBundle", + ":Assets", ], deps = [ "//submodules/Display", + "//submodules/MetalEngine", "//submodules/ComponentFlow", "//submodules/TelegramUI/Components/AnimatedTextComponent", - "//submodules/Utils/ShelfPack", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Airpods.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Airpods.imageset/Contents.json new file mode 100644 index 0000000000..0b0b845832 --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Airpods.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_call_audioairpods.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Airpods.imageset/ic_call_audioairpods.pdf b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Airpods.imageset/ic_call_audioairpods.pdf new file mode 100644 index 0000000000..2f31d498cc Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Airpods.imageset/ic_call_audioairpods.pdf differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsPro.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsPro.imageset/Contents.json new file mode 100644 index 0000000000..370377d8ee --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsPro.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_call_audioairpodspro.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsPro.imageset/ic_call_audioairpodspro.pdf b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsPro.imageset/ic_call_audioairpodspro.pdf new file mode 100644 index 0000000000..305f6ba52b Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsPro.imageset/ic_call_audioairpodspro.pdf differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsProMax.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsProMax.imageset/Contents.json new file mode 100644 index 0000000000..795a18ba91 --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsProMax.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_call_airpodsmax.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsProMax.imageset/ic_call_airpodsmax.pdf b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsProMax.imageset/ic_call_airpodsmax.pdf new file mode 100644 index 0000000000..3661b17240 Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/AirpodsProMax.imageset/ic_call_airpodsmax.pdf differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Bluetooth.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Bluetooth.imageset/Contents.json new file mode 100644 index 0000000000..a448d215b7 --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Bluetooth.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_call_audiobt.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Bluetooth.imageset/ic_call_audiobt.pdf b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Bluetooth.imageset/ic_call_audiobt.pdf new file mode 100644 index 0000000000..a2f8cad6cf Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Bluetooth.imageset/ic_call_audiobt.pdf differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/CallCancelIcon@2x.png b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/CallCancelIcon@2x.png new file mode 100644 index 0000000000..1d51eb1725 Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/CallCancelIcon@2x.png differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/CallCancelIcon@3x.png b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/CallCancelIcon@3x.png new file mode 100644 index 0000000000..95e99b4db2 Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/CallCancelIcon@3x.png differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/Contents.json new file mode 100644 index 0000000000..b9550f6ca9 --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/End.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "CallCancelIcon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "CallCancelIcon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Flip.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Flip.imageset/Contents.json new file mode 100644 index 0000000000..5c38ab0966 --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Flip.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_flip (1).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Flip.imageset/ic_flip (1).pdf b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Flip.imageset/ic_flip (1).pdf new file mode 100644 index 0000000000..da09156412 Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Flip.imageset/ic_flip (1).pdf differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Mute.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Mute.imageset/Contents.json new file mode 100644 index 0000000000..be885df064 --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Mute.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_call_microphone.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Mute.imageset/ic_call_microphone.pdf b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Mute.imageset/ic_call_microphone.pdf new file mode 100644 index 0000000000..99ad802193 Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Mute.imageset/ic_call_microphone.pdf differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Speaker.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Speaker.imageset/Contents.json new file mode 100644 index 0000000000..8876671fab --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Speaker.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_call_speaker.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Speaker.imageset/ic_call_speaker.pdf b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Speaker.imageset/ic_call_speaker.pdf new file mode 100644 index 0000000000..104b537f82 Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Speaker.imageset/ic_call_speaker.pdf differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Video.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Video.imageset/Contents.json new file mode 100644 index 0000000000..324f180936 --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Video.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_call_camera.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Video.imageset/ic_call_camera.pdf b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Video.imageset/ic_call_camera.pdf new file mode 100644 index 0000000000..92a55e486d Binary files /dev/null and b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Video.imageset/ic_call_camera.pdf differ diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Metal/Shaders.metal b/submodules/TelegramUI/Components/Calls/CallScreen/Metal/CallScreenShaders.metal similarity index 97% rename from submodules/TelegramUI/Components/Calls/CallScreen/Metal/Shaders.metal rename to submodules/TelegramUI/Components/Calls/CallScreen/Metal/CallScreenShaders.metal index 7bf649b0fc..1306472e92 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Metal/Shaders.metal +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Metal/CallScreenShaders.metal @@ -21,14 +21,6 @@ struct QuadVertexOut { float2 uv; }; -vertex float4 clearVertex(const device float2* vertexArray [[ buffer(0) ]], unsigned int vid [[ vertex_id ]]) { - return float4(vertexArray[vid], 0.0, 1.0); -} - -fragment half4 clearFragment(const device float4 &color [[ buffer(0) ]]) { - return half4(color); -} - vertex QuadVertexOut callBackgroundVertex( const device Rectangle &rect [[ buffer(0) ]], unsigned int vid [[ vertex_id ]] diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Animation/Animation.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Animation/Animation.swift index 531f7de1bf..daa9a69fdc 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Animation/Animation.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Animation/Animation.swift @@ -147,6 +147,18 @@ public final class AnimatedProperty: AnyAnimatedProp self.didStartAnimation?() } + public func animate(from: T, to: T, duration: Double, curve: AnimationCurve) { + self.value = from + self.animation = Animation(from: from, to: to, duration: duration, curve: curve) + self.animation?.start() + self.didStartAnimation?() + } + + public func set(to: T) { + self.animation = nil + self.value = to + } + override public func update() { if let animation = self.animation { self.value = animation.update(at: CACurrentMediaTime()) diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/CallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/CallScreen.swift index 2cc37ab552..6566d2eb64 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/CallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/CallScreen.swift @@ -1,4 +1,28 @@ import Foundation import UIKit import Display +import MetalKit +private final class BundleMarker: NSObject { +} + +private var metalLibraryValue: MTLLibrary? +func metalLibrary(device: MTLDevice) -> MTLLibrary? { + if let metalLibraryValue { + return metalLibraryValue + } + + let mainBundle = Bundle(for: BundleMarker.self) + guard let path = mainBundle.path(forResource: "CallScreenMetalSourcesBundle", ofType: "bundle") else { + return nil + } + guard let bundle = Bundle(path: path) else { + return nil + } + guard let library = try? device.makeDefaultLibrary(bundle: bundle) else { + return nil + } + + metalLibraryValue = library + return library +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/AvatarLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/AvatarLayer.swift index b0a316f966..f57ddb8ec4 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/AvatarLayer.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/AvatarLayer.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Display final class AvatarLayer: SimpleLayer { var image: UIImage? { diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift index 0d4b159d91..9d13c09cc2 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Display final class ButtonGroupView: UIView, ContentOverlayView { enum Key: Hashable { @@ -12,6 +13,7 @@ final class ButtonGroupView: UIView, ContentOverlayView { let overlayMaskLayer: CALayer private var buttons: [Key: ContentOverlayButton] = [:] + var audioPressed: (() -> Void)? var toggleVideo: (() -> Void)? override init(frame: CGRect) { @@ -89,9 +91,15 @@ final class ButtonGroupView: UIView, ContentOverlayView { let image: UIImage? switch key { case .audio: - image = UIImage(named: "Call/ButtonSpeaker") + image = UIImage(named: "Call/Speaker") + button.action = { [weak self] in + guard let self else { + return + } + self.audioPressed?() + } case .video: - image = UIImage(named: "Call/ButtonVideo") + image = UIImage(named: "Call/Video") button.action = { [weak self] in guard let self else { return @@ -99,9 +107,9 @@ final class ButtonGroupView: UIView, ContentOverlayView { self.toggleVideo?() } case .mic: - image = UIImage(named: "Call/ButtonMicMuted") + image = UIImage(named: "Call/Mute") case .close: - image = UIImage(named: "Call/ButtonEnd") + image = UIImage(named: "Call/End") } button.setImage(image?.withRenderingMode(.alwaysTemplate), for: .normal) diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBackgroundLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBackgroundLayer.swift index c6c1b5f497..e265111e22 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBackgroundLayer.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBackgroundLayer.swift @@ -1,6 +1,7 @@ import Foundation import MetalKit import UIKit +import MetalEngine private func shiftArray(array: [SIMD2], offset: Int) -> [SIMD2] { var newArray = array @@ -28,13 +29,28 @@ private func hexToFloat(_ hex: Int) -> SIMD4 { return SIMD4(x: red, y: green, z: blue, w: 1.0) } -final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject { - var internalData: MetalSubjectInternalData? +private struct ColorSet: Equatable, AnimationInterpolatable { + static let animationInterpolator = AnimationInterpolator { from, to, fraction in + var result: [SIMD4] = [] + for i in 0 ..< min(from.colors.count, to.colors.count) { + result.append(from.colors[i] * Float(1.0 - fraction) + to.colors[i] * Float(fraction)) + } + return ColorSet(colors: result) + } + + var colors: [SIMD4] +} + +final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject { + var internalData: MetalEngineSubjectInternalData? final class RenderState: RenderToLayerState { let pipelineState: MTLRenderPipelineState - init?(device: MTLDevice, library: MTLLibrary) { + init?(device: MTLDevice) { + guard let library = metalLibrary(device: device) else { + return nil + } guard let vertexFunction = library.makeFunction(name: "callBackgroundVertex"), let fragmentFunction = library.makeFunction(name: "callBackgroundFragment") else { return nil } @@ -66,11 +82,44 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject { private var displayLinkSubscription: SharedDisplayLink.Subscription? - var renderSpec: RenderLayerSpec? + var renderSpec: RenderLayerSpec? { + didSet { + if self.renderSpec != oldValue { + self.setNeedsUpdate() + } + } + } + + private let colorSets: [ColorSet] + private let colorTransition: AnimatedProperty + private var stateIndex: Int = 0 + private let phaseAcceleration = AnimatedProperty(0.0) init(isBlur: Bool) { self.isBlur = isBlur + self.colorSets = [ + ColorSet(colors: [ + hexToFloat(0x568FD6), + hexToFloat(0x626ED5), + hexToFloat(0xA667D5), + hexToFloat(0x7664DA) + ]), + ColorSet(colors: [ + hexToFloat(0xACBD65), + hexToFloat(0x459F8D), + hexToFloat(0x53A4D1), + hexToFloat(0x3E917A) + ]), + ColorSet(colors: [ + hexToFloat(0xC0508D), + hexToFloat(0xF09536), + hexToFloat(0xCE5081), + hexToFloat(0xFC7C4C) + ]) + ] + self.colorTransition = AnimatedProperty(colorSets[0]) + super.init() self.didEnterHierarchy = { [weak self] in @@ -81,8 +130,14 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject { guard let self else { return } + self.colorTransition.update() + self.phaseAcceleration.update() + let stepCount = 8 - self.phase = (self.phase + 1.0 / 60.0).truncatingRemainder(dividingBy: Float(stepCount)) + var phaseStep: CGFloat = 0.5 / 30.0 + phaseStep += phaseStep * self.phaseAcceleration.value * 0.5 + self.phase = (self.phase + Float(phaseStep)).truncatingRemainder(dividingBy: Float(stepCount)) + self.setNeedsUpdate() }) } @@ -95,6 +150,9 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject { } override init(layer: Any) { + self.colorSets = [] + self.colorTransition = AnimatedProperty(ColorSet(colors: [])) + super.init(layer: layer) } @@ -102,7 +160,19 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject { fatalError("init(coder:) has not been implemented") } - func update(context: MetalSubjectContext) { + func update(stateIndex: Int, animated: Bool) { + if self.stateIndex != stateIndex { + self.stateIndex = stateIndex + if animated { + self.phaseAcceleration.animate(from: 1.0, to: 0.0, duration: 2.0, curve: .easeInOut) + self.colorTransition.animate(to: self.colorSets[stateIndex % self.colorSets.count], duration: 0.3, curve: .easeInOut) + } else { + self.colorTransition.set(to: self.colorSets[stateIndex % self.colorSets.count]) + } + } + } + + func update(context: MetalEngineSubjectContext) { guard let renderSpec = self.renderSpec else { return } @@ -127,22 +197,9 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject { } encoder.setFragmentBytes(&positions, length: 4 * MemoryLayout>.size, index: 0) - var colors: [[SIMD4]] = [ - [ - hexToFloat(0x568FD6), - hexToFloat(0x626ED5), - hexToFloat(0xA667D5), - hexToFloat(0x7664DA) - ], - [ - hexToFloat(0xACBD65), - hexToFloat(0x459F8D), - hexToFloat(0x53A4D1), - hexToFloat(0x3E917A) - ] - ] + var colors: [SIMD4] = self.colorTransition.value.colors - encoder.setFragmentBytes(&colors[0], length: 4 * MemoryLayout>.size, index: 1) + encoder.setFragmentBytes(&colors, length: 4 * MemoryLayout>.size, index: 1) var brightness: Float = isBlur ? 1.1 : 1.0 var saturation: Float = isBlur ? 1.2 : 1.0 encoder.setFragmentBytes(&brightness, length: 4, index: 2) diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBlobsLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBlobsLayer.swift index 0d6732c4f2..cd988cbee6 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBlobsLayer.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBlobsLayer.swift @@ -1,8 +1,9 @@ import Foundation import MetalKit +import MetalEngine -final class CallBlobsLayer: MetalSubjectLayer, MetalSubject { - var internalData: MetalSubjectInternalData? +final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject { + var internalData: MetalEngineSubjectInternalData? struct Blob { var points: [Float] @@ -48,10 +49,10 @@ final class CallBlobsLayer: MetalSubjectLayer, MetalSubject { let pipelineState: MTLRenderPipelineState - required init?( - device: MTLDevice, - library: MTLLibrary - ) { + required init?(device: MTLDevice) { + guard let library = metalLibrary(device: device) else { + return nil + } guard let vertexFunction = library.makeFunction(name: "callBlobVertex"), let fragmentFunction = library.makeFunction(name: "callBlobFragment") else { return nil } @@ -123,7 +124,7 @@ final class CallBlobsLayer: MetalSubjectLayer, MetalSubject { fatalError("init(coder:) has not been implemented") } - func update(context: MetalSubjectContext) { + func update(context: MetalEngineSubjectContext) { if self.bounds.isEmpty { return } diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ContentOverlayButton.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ContentOverlayButton.swift index 880a6a41fd..e2ceaf3889 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ContentOverlayButton.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ContentOverlayButton.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Display final class ContentOverlayButton: UIButton, ContentOverlayView { var overlayMaskLayer: CALayer { diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ContentView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ContentView.swift index b735b03a43..822e5d9211 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ContentView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ContentView.swift @@ -1,7 +1,21 @@ import Foundation import UIKit +import Display +import MetalEngine final class ContentView: UIView { + private struct Params: Equatable { + var size: CGSize + var insets: UIEdgeInsets + var state: PrivateCallScreen.State + + init(size: CGSize, insets: UIEdgeInsets, state: PrivateCallScreen.State) { + self.size = size + self.insets = insets + self.state = state + } + } + private let blobLayer: CallBlobsLayer private let avatarLayer: AvatarLayer private let titleView: TextView @@ -14,8 +28,7 @@ final class ContentView: UIView { private var videoLayerMask: SimpleShapeLayer? private var blurredVideoLayerMask: SimpleShapeLayer? - private var currentLayout: (size: CGSize, insets: UIEdgeInsets)? - private var phase: CGFloat = 0.0 + private var params: Params? private var isDisplayingVideo: Bool = false private var videoDisplayFraction = AnimatedProperty(0.0) @@ -54,8 +67,8 @@ final class ContentView: UIView { guard let self else { return } - if let currentLayout = self.currentLayout { - self.update(size: currentLayout.size, insets: currentLayout.insets) + if let params = self.params { + self.updateInternal(params: params) } } } @@ -64,41 +77,59 @@ final class ContentView: UIView { fatalError("init(coder:) has not been implemented") } - func update(size: CGSize, insets: UIEdgeInsets) { - self.currentLayout = (size, insets) - + func update(size: CGSize, insets: UIEdgeInsets, state: PrivateCallScreen.State) { + let params = Params(size: size, insets: insets, state: state) + if self.params == params { + return + } + self.params = params + self.updateInternal(params: params) + } + + private func updateInternal(params: Params) { if self.emojiView == nil { let emojiView = KeyEmojiView(emoji: ["🐱", "🚂", "❄️", "🎨"]) self.emojiView = emojiView self.addSubview(emojiView) } if let emojiView = self.emojiView { - emojiView.frame = CGRect(origin: CGPoint(x: size.width - 12.0 - emojiView.size.width, y: insets.top + 27.0), size: emojiView.size) + emojiView.frame = CGRect(origin: CGPoint(x: params.size.width - 12.0 - emojiView.size.width, y: params.insets.top + 27.0), size: emojiView.size) } if self.videoInput == nil, let url = Bundle.main.url(forResource: "test2", withExtension: "mp4") { - self.videoInput = VideoInput(device: MetalContext.shared.device, url: url) + self.videoInput = VideoInput(device: MetalEngine.shared.device, url: url) self.videoLayer.video = self.videoInput } - self.phase += 3.0 / 60.0 - self.phase = self.phase.truncatingRemainder(dividingBy: 1.0) - var avatarScale: CGFloat = 0.05 * sin(CGFloat(self.phase) * CGFloat.pi) + //self.phase += 3.0 / 60.0 + //self.phase = self.phase.truncatingRemainder(dividingBy: 1.0) + var avatarScale: CGFloat = 0.05 * sin(CGFloat(0.0) * CGFloat.pi) avatarScale *= 1.0 - self.videoDisplayFraction.value let avatarSize: CGFloat = 136.0 let blobSize: CGFloat = 176.0 - let expandedVideoRadius: CGFloat = sqrt(pow(size.width * 0.5, 2.0) + pow(size.height * 0.5, 2.0)) + let expandedVideoRadius: CGFloat = sqrt(pow(params.size.width * 0.5, 2.0) + pow(params.size.height * 0.5, 2.0)) - let avatarFrame = CGRect(origin: CGPoint(x: floor((size.width - avatarSize) * 0.5), y: CGFloat.animationInterpolator.interpolate(from: 222.0, to: floor((size.height - avatarSize) * 0.5), fraction: self.videoDisplayFraction.value)), size: CGSize(width: avatarSize, height: avatarSize)) + let avatarFrame = CGRect(origin: CGPoint(x: floor((params.size.width - avatarSize) * 0.5), y: CGFloat.animationInterpolator.interpolate(from: 222.0, to: floor((params.size.height - avatarSize) * 0.5), fraction: self.videoDisplayFraction.value)), size: CGSize(width: avatarSize, height: avatarSize)) - let titleSize = self.titleView.update(string: "Emma Walters", fontSize: CGFloat.animationInterpolator.interpolate(from: 28.0, to: 17.0, fraction: self.videoDisplayFraction.value), fontWeight: CGFloat.animationInterpolator.interpolate(from: 0.0, to: 0.25, fraction: self.videoDisplayFraction.value), constrainedWidth: size.width - 16.0 * 2.0) - let titleFrame = CGRect(origin: CGPoint(x: (size.width - titleSize.width) * 0.5, y: CGFloat.animationInterpolator.interpolate(from: avatarFrame.maxY + 39.0, to: insets.top + 17.0, fraction: self.videoDisplayFraction.value)), size: titleSize) + let titleSize = self.titleView.update(string: "Emma Walters", fontSize: CGFloat.animationInterpolator.interpolate(from: 28.0, to: 17.0, fraction: self.videoDisplayFraction.value), fontWeight: CGFloat.animationInterpolator.interpolate(from: 0.0, to: 0.25, fraction: self.videoDisplayFraction.value), constrainedWidth: params.size.width - 16.0 * 2.0) + let titleFrame = CGRect(origin: CGPoint(x: (params.size.width - titleSize.width) * 0.5, y: CGFloat.animationInterpolator.interpolate(from: avatarFrame.maxY + 39.0, to: params.insets.top + 17.0, fraction: self.videoDisplayFraction.value)), size: titleSize) self.titleView.frame = titleFrame - let statusSize = self.statusView.update(state: .waiting(.requesting)) - self.statusView.frame = CGRect(origin: CGPoint(x: (size.width - statusSize.width) * 0.5, y: titleFrame.maxY + CGFloat.animationInterpolator.interpolate(from: 4.0, to: 0.0, fraction: self.videoDisplayFraction.value)), size: statusSize) + let statusState: StatusView.State + switch params.state.lifecycleState { + case .connecting: + statusState = .waiting(.requesting) + case .ringing: + statusState = .waiting(.ringing) + case .exchangingKeys: + statusState = .waiting(.generatingKeys) + case let .active(_, signalInfo): + statusState = .active(StatusView.ActiveState(signalStrength: signalInfo.quality)) + } + let statusSize = self.statusView.update(state: statusState) + self.statusView.frame = CGRect(origin: CGPoint(x: (params.size.width - statusSize.width) * 0.5, y: titleFrame.maxY + CGFloat.animationInterpolator.interpolate(from: 4.0, to: 0.0, fraction: self.videoDisplayFraction.value)), size: statusSize) let blobFrame = CGRect(origin: CGPoint(x: floor(avatarFrame.midX - blobSize * 0.5), y: floor(avatarFrame.midY - blobSize * 0.5)), size: CGSize(width: blobSize, height: blobSize)) @@ -114,7 +145,7 @@ final class ContentView: UIView { self.blobLayer.transform = CATransform3DMakeScale(1.0 + avatarScale * 2.0, 1.0 + avatarScale * 2.0, 1.0) let videoResolution = CGSize(width: 400.0, height: 400.0) - let videoFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) + let videoFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: params.size) let videoRenderingSize = CGSize(width: videoResolution.width * 2.0, height: videoResolution.height * 2.0) diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/MainVideoLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/MainVideoLayer.swift index c360549739..0be79faf8d 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/MainVideoLayer.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/MainVideoLayer.swift @@ -3,6 +3,7 @@ import UIKit import MetalKit import MetalPerformanceShaders import Accelerate +import MetalEngine func imageToCVPixelBuffer(image: UIImage) -> CVPixelBuffer? { guard let cgImage = image.cgImage, let data = cgImage.dataProvider?.data, let bytes = CFDataGetBytePtr(data), let colorSpace = cgImage.colorSpace, case .rgb = colorSpace.model, cgImage.bitsPerPixel / cgImage.bitsPerComponent == 4 else { @@ -36,10 +37,10 @@ func imageToCVPixelBuffer(image: UIImage) -> CVPixelBuffer? { return pixelBuffer } -final class MainVideoLayer: MetalSubjectLayer, MetalSubject { - var internalData: MetalSubjectInternalData? +final class MainVideoLayer: MetalEngineSubjectLayer, MetalEngineSubject { + var internalData: MetalEngineSubjectInternalData? - let blurredLayer: MetalSubjectLayer + let blurredLayer: MetalEngineSubjectLayer final class BlurState: ComputeState { let computePipelineStateYUVToRGBA: MTLComputePipelineState @@ -47,10 +48,10 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject { let computePipelineStateVertical: MTLComputePipelineState let downscaleKernel: MPSImageBilinearScale - required init?( - device: MTLDevice, - library: MTLLibrary - ) { + required init?(device: MTLDevice) { + guard let library = metalLibrary(device: device) else { + return nil + } guard let functionVideoYUVToRGBA = library.makeFunction(name: "videoYUVToRGBA") else { return nil } @@ -79,10 +80,10 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject { final class RenderState: RenderToLayerState { let pipelineState: MTLRenderPipelineState - required init?( - device: MTLDevice, - library: MTLLibrary - ) { + required init?(device: MTLDevice) { + guard let library = metalLibrary(device: device) else { + return nil + } guard let vertexFunction = library.makeFunction(name: "mainVideoVertex"), let fragmentFunction = library.makeFunction(name: "mainVideoFragment") else { return nil } @@ -115,13 +116,13 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject { private var blurredVerticalTexture: PooledTexture? override init() { - self.blurredLayer = MetalSubjectLayer() + self.blurredLayer = MetalEngineSubjectLayer() super.init() } override init(layer: Any) { - self.blurredLayer = MetalSubjectLayer() + self.blurredLayer = MetalEngineSubjectLayer() super.init(layer: layer) } @@ -130,7 +131,7 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject { fatalError("init(coder:) has not been implemented") } - func update(context: MetalSubjectContext) { + func update(context: MetalEngineSubjectContext) { if self.isHidden { return } @@ -143,16 +144,16 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject { let rgbaTextureSpec = TextureSpec(width: videoTextures.y.width, height: videoTextures.y.height, pixelFormat: .rgba8UnsignedNormalized) if self.rgbaTexture == nil || self.rgbaTexture?.spec != rgbaTextureSpec { - self.rgbaTexture = MetalContext.shared.pooledTexture(spec: rgbaTextureSpec) + self.rgbaTexture = MetalEngine.shared.pooledTexture(spec: rgbaTextureSpec) } if self.downscaledTexture == nil { - self.downscaledTexture = MetalContext.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized)) + self.downscaledTexture = MetalEngine.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized)) } if self.blurredHorizontalTexture == nil { - self.blurredHorizontalTexture = MetalContext.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized)) + self.blurredHorizontalTexture = MetalEngine.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized)) } if self.blurredVerticalTexture == nil { - self.blurredVerticalTexture = MetalContext.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized)) + self.blurredVerticalTexture = MetalEngine.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized)) } guard let rgbaTexture = self.rgbaTexture?.get(context: context) else { diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/StatusView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/StatusView.swift index 24b6f5ba7d..0725044128 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/StatusView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/StatusView.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Display private final class AnimatedDotsLayer: SimpleLayer { private let dotLayers: [SimpleLayer] @@ -76,8 +77,18 @@ final class StatusView: UIView { case ringing case generatingKeys } + + struct ActiveState { + var signalStrength: Double + + init(signalStrength: Double) { + self.signalStrength = signalStrength + } + } + enum State { case waiting(WaitingState) + case active(ActiveState) } private var textView: TextView @@ -110,6 +121,9 @@ final class StatusView: UIView { case .generatingKeys: textString = "Exchanging encryption keys" } + case let .active(activeState): + textString = "0:00" + let _ = activeState } let textSize = self.textView.update(string: textString, fontSize: 16.0, fontWeight: 0.0, constrainedWidth: 200.0) self.textView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: textSize) diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/ContentOverlayLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/ContentOverlayLayer.swift index 7e29a77a13..445f16f6c4 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/ContentOverlayLayer.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/ContentOverlayLayer.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Display final class ContentOverlayLayer: SimpleLayer { private struct Params: Equatable { diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/MirroringLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/MirroringLayer.swift index 410c8ddb37..801d6c06e0 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/MirroringLayer.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/MirroringLayer.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Display final class MirroringLayer: SimpleLayer { var targetLayer: SimpleLayer? diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index 1653cbb78f..059df289dc 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -1,7 +1,42 @@ import Foundation import UIKit +import Display +import MetalEngine public final class PrivateCallScreen: UIView { + public struct State: Equatable { + public struct SignalInfo: Equatable { + public var quality: Double + + public init(quality: Double) { + self.quality = quality + } + } + + public enum LifecycleState: Equatable { + case connecting + case ringing + case exchangingKeys + case active(startTime: Double, signalInfo: SignalInfo) + } + + public var lifecycleState: LifecycleState + + public init(lifecycleState: LifecycleState) { + self.lifecycleState = lifecycleState + } + } + + private struct Params: Equatable { + var size: CGSize + var insets: UIEdgeInsets + + init(size: CGSize, insets: UIEdgeInsets) { + self.size = size + self.insets = insets + } + } + private let backgroundLayer: CallBackgroundLayer private let contentOverlayLayer: ContentOverlayLayer private let contentOverlayContainer: ContentOverlayContainer @@ -13,6 +48,18 @@ public final class PrivateCallScreen: UIView { private let buttonGroupView: ButtonGroupView + public var state: State = State(lifecycleState: .connecting) { + didSet { + if self.state != oldValue { + if let params = self.params { + self.updateInternal(params: params, animated: true) + } + } + } + } + + private var params: Params? + public override init(frame: CGRect) { self.blurContentsLayer = SimpleLayer() @@ -44,6 +91,26 @@ public final class PrivateCallScreen: UIView { self.contentOverlayContainer.addSubview(self.buttonGroupView) + self.buttonGroupView.audioPressed = { [weak self] in + guard let self else { + return + } + + var state = self.state + switch state.lifecycleState { + case .connecting, .ringing, .exchangingKeys: + state.lifecycleState = .active(startTime: CFAbsoluteTimeGetCurrent(), signalInfo: State.SignalInfo(quality: 1.0)) + case let .active(startTime, signalInfo): + if signalInfo.quality == 1.0 { + state.lifecycleState = .active(startTime: startTime, signalInfo: State.SignalInfo(quality: 0.2)) + } else if signalInfo.quality == 0.2 { + state.lifecycleState = .connecting + } + + } + self.state = state + } + self.buttonGroupView.toggleVideo = { [weak self] in guard let self else { return @@ -65,9 +132,18 @@ public final class PrivateCallScreen: UIView { } public func update(size: CGSize, insets: UIEdgeInsets) { - let backgroundFrame = CGRect(origin: CGPoint(), size: size) + let params = Params(size: size, insets: insets) + if self.params == params { + return + } + self.params = params + self.updateInternal(params: params, animated: false) + } - let aspect: CGFloat = size.width / size.height + private func updateInternal(params: Params, animated: Bool) { + let backgroundFrame = CGRect(origin: CGPoint(), size: params.size) + + let aspect: CGFloat = params.size.width / params.size.height let sizeNorm: CGFloat = 64.0 let renderingSize = CGSize(width: floor(sizeNorm * aspect), height: sizeNorm) let edgeSize: Int = 2 @@ -77,18 +153,36 @@ public final class PrivateCallScreen: UIView { self.backgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2)) self.backgroundLayer.frame = visualBackgroundFrame - self.contentOverlayLayer.frame = CGRect(origin: CGPoint(), size: size) - self.contentOverlayLayer.update(size: size, contentInsets: UIEdgeInsets()) + let backgroundStateIndex: Int + switch self.state.lifecycleState { + case .connecting: + backgroundStateIndex = 0 + case .ringing: + backgroundStateIndex = 0 + case .exchangingKeys: + backgroundStateIndex = 0 + case let .active(_, signalInfo): + if signalInfo.quality <= 0.2 { + backgroundStateIndex = 2 + } else { + backgroundStateIndex = 1 + } + } + self.backgroundLayer.update(stateIndex: backgroundStateIndex, animated: animated) - self.contentOverlayContainer.frame = CGRect(origin: CGPoint(), size: size) + self.contentOverlayLayer.frame = CGRect(origin: CGPoint(), size: params.size) + self.contentOverlayLayer.update(size: params.size, contentInsets: UIEdgeInsets()) + + self.contentOverlayContainer.frame = CGRect(origin: CGPoint(), size: params.size) self.blurBackgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2)) self.blurBackgroundLayer.frame = visualBackgroundFrame + self.blurBackgroundLayer.update(stateIndex: backgroundStateIndex, animated: animated) - self.buttonGroupView.frame = CGRect(origin: CGPoint(), size: size) - self.buttonGroupView.update(size: size) + self.buttonGroupView.frame = CGRect(origin: CGPoint(), size: params.size) + self.buttonGroupView.update(size: params.size) - self.contentView.frame = CGRect(origin: CGPoint(), size: size) - self.contentView.update(size: size, insets: insets) + self.contentView.frame = CGRect(origin: CGPoint(), size: params.size) + self.contentView.update(size: params.size, insets: params.insets, state: self.state) } } diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/SimpleLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/SimpleLayer.swift deleted file mode 100644 index 4bab9bcb80..0000000000 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/SimpleLayer.swift +++ /dev/null @@ -1,67 +0,0 @@ -import Foundation -import UIKit - -public final class NullActionClass: NSObject, CAAction { - @objc public func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { - } -} - -public let nullAction = NullActionClass() - -open class SimpleLayer: CALayer { - public var didEnterHierarchy: (() -> Void)? - public var didExitHierarchy: (() -> Void)? - public private(set) var isInHierarchy: Bool = false - - override open func action(forKey event: String) -> CAAction? { - if event == kCAOnOrderIn { - self.isInHierarchy = true - self.didEnterHierarchy?() - } else if event == kCAOnOrderOut { - self.isInHierarchy = false - self.didExitHierarchy?() - } - return nullAction - } - - override public init() { - super.init() - } - - override public init(layer: Any) { - super.init(layer: layer) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -open class SimpleShapeLayer: CAShapeLayer { - public var didEnterHierarchy: (() -> Void)? - public var didExitHierarchy: (() -> Void)? - public private(set) var isInHierarchy: Bool = false - - override open func action(forKey event: String) -> CAAction? { - if event == kCAOnOrderIn { - self.isInHierarchy = true - self.didEnterHierarchy?() - } else if event == kCAOnOrderOut { - self.isInHierarchy = false - self.didExitHierarchy?() - } - return nullAction - } - - override public init() { - super.init() - } - - override public init(layer: Any) { - super.init(layer: layer) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 159dc94d10..75737a2d13 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -41,6 +41,7 @@ import DeviceProximity import MediaEditor import TelegramUIDeclareEncodables import ContextMenuScreen +import MetalEngine #if canImport(AppCenter) import AppCenter @@ -341,6 +342,8 @@ private func extractAccountManagerState(records: AccountRecordsView