mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Animation rendering updates
This commit is contained in:
parent
c8bc4b7f12
commit
05a0d399dd
@ -172,7 +172,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
private var cachedData: (Data, Bool, EmojiFitzModifier?)?
|
private var cachedData: (Data, Bool, EmojiFitzModifier?)?
|
||||||
|
|
||||||
private let useMetalCache: Bool
|
private let useMetalCache: Bool
|
||||||
private var renderer: (AnimationRenderer & ASDisplayNode)?
|
private var renderer: AnimationRendererPool.Holder?
|
||||||
|
|
||||||
public var isPlaying: Bool = false
|
public var isPlaying: Bool = false
|
||||||
private var currentLoopCount: Int = 0
|
private var currentLoopCount: Int = 0
|
||||||
@ -232,30 +232,42 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
self.timer.swap(nil)?.invalidate()
|
self.timer.swap(nil)?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static let hardwareRendererPool = AnimationRendererPool(generate: {
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
return CompressedAnimationRenderer()
|
||||||
|
} else {
|
||||||
|
return SoftwareAnimationRenderer()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
private static let softwareRendererPool = AnimationRendererPool(generate: {
|
||||||
|
return SoftwareAnimationRenderer()
|
||||||
|
})
|
||||||
|
|
||||||
private weak var nodeToCopyFrameFrom: AnimatedStickerNode?
|
private weak var nodeToCopyFrameFrom: AnimatedStickerNode?
|
||||||
override public func didLoad() {
|
override public func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
if #available(iOS 10.0, *), self.useMetalCache {
|
if #available(iOS 10.0, *), self.useMetalCache {
|
||||||
self.renderer = CompressedAnimationRenderer()
|
self.renderer = AnimatedStickerNode.hardwareRendererPool.take()
|
||||||
} else {
|
} else {
|
||||||
self.renderer = SoftwareAnimationRenderer()
|
self.renderer = AnimatedStickerNode.softwareRendererPool.take()
|
||||||
|
if let contents = self.nodeToCopyFrameFrom?.renderer?.renderer.contents {
|
||||||
|
self.renderer?.renderer.contents = contents
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.size ?? self.bounds.size)
|
self.renderer?.renderer.frame = CGRect(origin: CGPoint(), size: self.size ?? self.bounds.size)
|
||||||
if let contents = self.nodeToCopyFrameFrom?.renderer?.contents {
|
|
||||||
self.renderer?.contents = contents
|
|
||||||
}
|
|
||||||
if let (overlayColor, replace) = self.overlayColor {
|
if let (overlayColor, replace) = self.overlayColor {
|
||||||
self.renderer?.setOverlayColor(overlayColor, replace: replace, animated: false)
|
self.renderer?.renderer.setOverlayColor(overlayColor, replace: replace, animated: false)
|
||||||
}
|
}
|
||||||
self.nodeToCopyFrameFrom = nil
|
self.nodeToCopyFrameFrom = nil
|
||||||
self.addSubnode(self.renderer!)
|
self.addSubnode(self.renderer!.renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) {
|
public func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) {
|
||||||
if let renderer = self.renderer {
|
if let renderer = self.renderer?.renderer as? SoftwareAnimationRenderer, let otherRenderer = otherNode?.renderer?.renderer as? SoftwareAnimationRenderer {
|
||||||
if let contents = otherNode?.renderer?.contents {
|
if let contents = otherRenderer.contents {
|
||||||
renderer.contents = contents
|
renderer.contents = contents
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -428,7 +440,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.renderer?.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, mulAlpha: frame.multiplyAlpha, completion: {
|
strongSelf.renderer?.renderer.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, mulAlpha: frame.multiplyAlpha, completion: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -532,7 +544,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.renderer?.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, mulAlpha: frame.multiplyAlpha, completion: {
|
strongSelf.renderer?.renderer.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, mulAlpha: frame.multiplyAlpha, completion: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -685,7 +697,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.renderer?.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, mulAlpha: frame.multiplyAlpha, completion: {
|
strongSelf.renderer?.renderer.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, mulAlpha: frame.multiplyAlpha, completion: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -715,11 +727,11 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
|
|
||||||
public func updateLayout(size: CGSize) {
|
public func updateLayout(size: CGSize) {
|
||||||
self.size = size
|
self.size = size
|
||||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: size)
|
self.renderer?.renderer.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
self.overlayColor = (color, replace)
|
self.overlayColor = (color, replace)
|
||||||
self.renderer?.setOverlayColor(color, replace: replace, animated: animated)
|
self.renderer?.renderer.setOverlayColor(color, replace: replace, animated: animated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,47 @@ public enum AnimationRendererFrameType {
|
|||||||
case dct
|
case dct
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol AnimationRenderer {
|
final class AnimationRendererPool {
|
||||||
|
final class Holder {
|
||||||
|
let pool: AnimationRendererPool
|
||||||
|
let renderer: AnimationRenderer
|
||||||
|
|
||||||
|
init(pool: AnimationRendererPool, renderer: AnimationRenderer) {
|
||||||
|
self.pool = pool
|
||||||
|
self.renderer = renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.renderer.removeFromSupernode()
|
||||||
|
self.pool.putBack(renderer: self.renderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let generate: () -> AnimationRenderer
|
||||||
|
|
||||||
|
private var items: [AnimationRenderer] = []
|
||||||
|
|
||||||
|
init(generate: @escaping () -> AnimationRenderer) {
|
||||||
|
self.generate = generate
|
||||||
|
}
|
||||||
|
|
||||||
|
func take() -> Holder {
|
||||||
|
if !self.items.isEmpty {
|
||||||
|
let item = self.items.removeLast()
|
||||||
|
return Holder(pool: self, renderer: item)
|
||||||
|
} else {
|
||||||
|
return Holder(pool: self, renderer: self.generate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func putBack(renderer: AnimationRenderer) {
|
||||||
|
#if DEBUG
|
||||||
|
self.items.append(renderer)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol AnimationRenderer: ASDisplayNode {
|
||||||
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void)
|
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void)
|
||||||
|
|
||||||
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool)
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool)
|
||||||
|
@ -88,66 +88,6 @@ final class CompressedAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
|||||||
case .yuva:
|
case .yuva:
|
||||||
self.renderer.renderYuva(metalLayer: self.layer, width: width, height: height, data: data, completion: completion)
|
self.renderer.renderYuva(metalLayer: self.layer, width: width, height: height, data: data, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*assert(bytesPerRow > 0)
|
|
||||||
queue.async { [weak self] in
|
|
||||||
switch type {
|
|
||||||
case .dct:
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var image: UIImage?
|
|
||||||
|
|
||||||
autoreleasepool {
|
|
||||||
image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData, contextBytesPerRow in
|
|
||||||
switch type {
|
|
||||||
case .yuva:
|
|
||||||
data.withUnsafeBytes { bytes -> Void in
|
|
||||||
guard let baseAddress = bytes.baseAddress else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if bytesPerRow <= 0 || height <= 0 || width <= 0 || bytesPerRow * height > bytes.count {
|
|
||||||
assert(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
decodeYUVAToRGBA(baseAddress.assumingMemoryBound(to: UInt8.self), pixelData, Int32(width), Int32(height), Int32(contextBytesPerRow))
|
|
||||||
}
|
|
||||||
case .argb:
|
|
||||||
var data = data
|
|
||||||
data.withUnsafeMutableBytes { bytes -> Void in
|
|
||||||
guard let baseAddress = bytes.baseAddress else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if mulAlpha {
|
|
||||||
var srcData = vImage_Buffer(data: baseAddress.assumingMemoryBound(to: UInt8.self), height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
|
|
||||||
var destData = vImage_Buffer(data: pixelData, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
|
|
||||||
|
|
||||||
let permuteMap: [UInt8] = [3, 2, 1, 0]
|
|
||||||
vImagePermuteChannels_ARGB8888(&srcData, &destData, permuteMap, vImage_Flags(kvImageDoNotTile))
|
|
||||||
vImagePremultiplyData_ARGB8888(&destData, &destData, vImage_Flags(kvImageDoNotTile))
|
|
||||||
vImagePermuteChannels_ARGB8888(&destData, &destData, permuteMap, vImage_Flags(kvImageDoNotTile))
|
|
||||||
} else {
|
|
||||||
memcpy(pixelData, baseAddress.assumingMemoryBound(to: UInt8.self), bytes.count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.contents = image?.cgImage
|
|
||||||
strongSelf.updateHighlightedContentNode()
|
|
||||||
if strongSelf.highlightedContentNode?.frame != strongSelf.bounds {
|
|
||||||
strongSelf.highlightedContentNode?.frame = strongSelf.bounds
|
|
||||||
}
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateHighlightedContentNode() {
|
private func updateHighlightedContentNode() {
|
||||||
|
@ -75,7 +75,9 @@ fragment float4 samplingIdctShader(
|
|||||||
|
|
||||||
const half4 yuva = half4(color0, color1, color2, color3);
|
const half4 yuva = half4(color0, color1, color2, color3);
|
||||||
|
|
||||||
return float4(rgb(yuva));
|
const half4 color = rgb(yuva);
|
||||||
|
|
||||||
|
return float4(color.r * color.a, color.g * color.a, color.b * color.a, color.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment float4 samplingRgbShader(
|
fragment float4 samplingRgbShader(
|
||||||
@ -84,7 +86,11 @@ fragment float4 samplingRgbShader(
|
|||||||
) {
|
) {
|
||||||
constexpr sampler textureSampler(mag_filter::linear, min_filter::linear);
|
constexpr sampler textureSampler(mag_filter::linear, min_filter::linear);
|
||||||
|
|
||||||
const half4 color = colorTexture.sample(textureSampler, in.textureCoordinate);
|
half4 color = colorTexture.sample(textureSampler, in.textureCoordinate);
|
||||||
|
|
||||||
|
color.r *= color.a;
|
||||||
|
color.g *= color.a;
|
||||||
|
color.b *= color.a;
|
||||||
|
|
||||||
return float4(color.r, color.g, color.b, color.a);
|
return float4(color.r, color.g, color.b, color.a);
|
||||||
}
|
}
|
||||||
@ -104,27 +110,33 @@ fragment float4 samplingYuvaShader(
|
|||||||
RasterizerData in [[stage_in]],
|
RasterizerData in [[stage_in]],
|
||||||
texture2d<half, access::sample> yTexture [[texture(0)]],
|
texture2d<half, access::sample> yTexture [[texture(0)]],
|
||||||
texture2d<half, access::sample> cbcrTexture [[texture(1)]],
|
texture2d<half, access::sample> cbcrTexture [[texture(1)]],
|
||||||
texture2d<uint, access::sample> alphaTexture [[texture(2)]],
|
texture2d<uint, access::read> alphaTexture [[texture(2)]],
|
||||||
constant int &alphaWidth [[buffer(3)]]
|
constant uint2 &alphaSize [[buffer(3)]]
|
||||||
) {
|
) {
|
||||||
constexpr sampler textureSampler(mag_filter::linear, min_filter::linear);
|
constexpr sampler textureSampler(mag_filter::linear, min_filter::linear);
|
||||||
constexpr sampler nearestSampler(mag_filter::nearest, min_filter::nearest);
|
|
||||||
|
|
||||||
half4 color = samplePoint(yTexture, cbcrTexture, textureSampler, in.textureCoordinate);
|
half4 color = samplePoint(yTexture, cbcrTexture, textureSampler, in.textureCoordinate);
|
||||||
|
|
||||||
int horizontalPixel = (int)(in.textureCoordinate.x * alphaWidth);
|
int alphaX = (int)(in.textureCoordinate.x * alphaSize.x);
|
||||||
uint8_t alpha2 = (uint8_t)alphaTexture.sample(nearestSampler, in.textureCoordinate.x, in.textureCoordinate.y).r;
|
int alphaY = (int)(in.textureCoordinate.y * alphaSize.y);
|
||||||
|
|
||||||
uint8_t a1 = (alpha2 & (0xf0U));
|
uint32_t packedAlpha = alphaTexture.read(uint2(alphaX / 2, alphaY)).r;
|
||||||
uint8_t a2 = ((alpha2 & (0x0fU)) << 4);
|
uint32_t a1 = (packedAlpha & (0xf0U));
|
||||||
|
uint32_t a2 = (packedAlpha & (0x0fU)) << 4;
|
||||||
|
|
||||||
uint8_t alphas[2];
|
uint32_t left = (a1 >> 4) | a1;
|
||||||
alphas[0] = a1 | (a1 >> 4);
|
uint32_t right = (a2 >> 4) | a2;
|
||||||
alphas[1] = a2 | (a2 >> 4);
|
|
||||||
int alphaIndex = horizontalPixel % 2;
|
|
||||||
uint8_t resolvedAlpha = alphas[alphaIndex];
|
|
||||||
|
|
||||||
color.a = ((half)resolvedAlpha) / 255.0f;
|
uint32_t chooseLeft = alphaX % 2 == 0;
|
||||||
|
uint32_t resolvedAlpha = chooseLeft * left + (1 - chooseLeft) * right;
|
||||||
|
|
||||||
|
float alpha = resolvedAlpha / 255.0f;
|
||||||
|
|
||||||
|
color.r *= alpha;
|
||||||
|
color.g *= alpha;
|
||||||
|
color.b *= alpha;
|
||||||
|
|
||||||
|
color.a = alpha;
|
||||||
|
|
||||||
return float4(color);
|
return float4(color);
|
||||||
}
|
}
|
||||||
|
@ -131,17 +131,12 @@ final class Texture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readDirect(width: Int, height: Int, bytesPerRow: Int, read: (UnsafeMutableRawPointer, Int) -> Void) {
|
func readDirect(width: Int, height: Int, bytesPerRow: Int, read: (UnsafeMutableRawPointer?) -> UnsafeRawPointer) {
|
||||||
if let directBuffer = self.directBuffer, width == self.width, height == self.height, bytesPerRow == directBuffer.bytesPerRow {
|
if let directBuffer = self.directBuffer, width == self.width, height == self.height, bytesPerRow == directBuffer.bytesPerRow {
|
||||||
read(directBuffer.buffer.contents(), directBuffer.buffer.length)
|
let _ = read(directBuffer.buffer.contents())
|
||||||
} else {
|
} else {
|
||||||
var tempData = Data(count: height * bytesPerRow)
|
|
||||||
tempData.withUnsafeMutableBytes { bytes in
|
|
||||||
read(bytes.baseAddress!, height * bytesPerRow)
|
|
||||||
|
|
||||||
let region = MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize(width: width, height: height, depth: 1))
|
let region = MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize(width: width, height: height, depth: 1))
|
||||||
self.texture.replace(region: region, mipmapLevel: 0, withBytes: bytes.baseAddress!, bytesPerRow: bytesPerRow)
|
self.texture.replace(region: region, mipmapLevel: 0, withBytes: read(nil), bytesPerRow: bytesPerRow)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ public final class CompressedImageRenderer {
|
|||||||
let renderIdctPipelineState: MTLRenderPipelineState
|
let renderIdctPipelineState: MTLRenderPipelineState
|
||||||
let renderRgbPipelineState: MTLRenderPipelineState
|
let renderRgbPipelineState: MTLRenderPipelineState
|
||||||
let renderYuvaPipelineState: MTLRenderPipelineState
|
let renderYuvaPipelineState: MTLRenderPipelineState
|
||||||
let commandQueue: MTLCommandQueue
|
|
||||||
|
|
||||||
init?(sharedContext: AnimationCompressor.SharedContext) {
|
init?(sharedContext: AnimationCompressor.SharedContext) {
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
@ -81,11 +80,6 @@ public final class CompressedImageRenderer {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self.renderYuvaPipelineState = renderYuvaPipelineState
|
self.renderYuvaPipelineState = renderYuvaPipelineState
|
||||||
|
|
||||||
guard let commandQueue = self.sharedContext.device.makeCommandQueue() else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
self.commandQueue = commandQueue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,9 +93,60 @@ public final class CompressedImageRenderer {
|
|||||||
|
|
||||||
private var yuvaTextures: TextureSet?
|
private var yuvaTextures: TextureSet?
|
||||||
|
|
||||||
|
private let commandQueue: MTLCommandQueue
|
||||||
|
|
||||||
public init?(sharedContext: AnimationCompressor.SharedContext) {
|
public init?(sharedContext: AnimationCompressor.SharedContext) {
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
self.shared = Shared.shared
|
self.shared = Shared.shared
|
||||||
|
|
||||||
|
guard let commandQueue = self.sharedContext.device.makeCommandQueue() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.commandQueue = commandQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
private var drawableRequestTimestamp: Double?
|
||||||
|
|
||||||
|
private func getNextDrawable(metalLayer: CALayer, drawableSize: CGSize) -> CAMetalDrawable? {
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
if let metalLayer = metalLayer as? CAMetalLayer {
|
||||||
|
if metalLayer.drawableSize != drawableSize {
|
||||||
|
metalLayer.drawableSize = drawableSize
|
||||||
|
}
|
||||||
|
return metalLayer.nextDrawable()
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if let metalLayer = metalLayer as? CAMetalLayer {
|
||||||
|
if metalLayer.drawableSize != drawableSize {
|
||||||
|
metalLayer.drawableSize = drawableSize
|
||||||
|
}
|
||||||
|
let beginTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
let drawableRequestDuration: Double
|
||||||
|
if let drawableRequestTimestamp = self.drawableRequestTimestamp {
|
||||||
|
drawableRequestDuration = beginTime - drawableRequestTimestamp
|
||||||
|
if drawableRequestDuration < 1.0 / 60.0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
drawableRequestDuration = 0.0
|
||||||
|
}
|
||||||
|
self.drawableRequestTimestamp = beginTime
|
||||||
|
let result = metalLayer.nextDrawable()
|
||||||
|
let duration = CFAbsoluteTimeGetCurrent() - beginTime
|
||||||
|
if duration > 1.0 / 200.0 {
|
||||||
|
print("lag \(duration * 1000.0) ms (\(drawableRequestDuration * 1000.0) ms)")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateIdctTextures(compressedImage: AnimationCompressor.CompressedImageData) {
|
private func updateIdctTextures(compressedImage: AnimationCompressor.CompressedImageData) {
|
||||||
@ -162,8 +207,18 @@ public final class CompressedImageRenderer {
|
|||||||
let planeSize = Int(readBuffer.readInt32())
|
let planeSize = Int(readBuffer.readInt32())
|
||||||
let planeData = readBuffer.readDataNoCopy(length: planeSize)
|
let planeData = readBuffer.readDataNoCopy(length: planeSize)
|
||||||
|
|
||||||
compressedTextures.textures[i].readDirect(width: planeWidth, height: planeHeight, bytesPerRow: bytesPerRow, read: { destination, maxLength in
|
var tempData: Data?
|
||||||
readDCTBlocks(Int32(planeWidth), Int32(planeHeight), planeData, destination.assumingMemoryBound(to: Float32.self), Int32(bytesPerRow / 4))
|
compressedTextures.textures[i].readDirect(width: planeWidth, height: planeHeight, bytesPerRow: bytesPerRow, read: { destinationBytes in
|
||||||
|
if let destinationBytes = destinationBytes {
|
||||||
|
readDCTBlocks(Int32(planeWidth), Int32(planeHeight), planeData, destinationBytes.assumingMemoryBound(to: Float32.self), Int32(bytesPerRow / 4))
|
||||||
|
return UnsafeRawPointer(destinationBytes)
|
||||||
|
} else {
|
||||||
|
tempData = Data(count: bytesPerRow * planeHeight)
|
||||||
|
return tempData!.withUnsafeMutableBytes { bytes -> UnsafeRawPointer in
|
||||||
|
readDCTBlocks(Int32(planeWidth), Int32(planeHeight), planeData, bytes.baseAddress!.assumingMemoryBound(to: Float32.self), Int32(bytesPerRow / 4))
|
||||||
|
return UnsafeRawPointer(bytes.baseAddress!)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,7 +232,7 @@ public final class CompressedImageRenderer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let commandBuffer = self.shared.commandQueue.makeCommandBuffer() else {
|
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
commandBuffer.label = "MyCommand"
|
commandBuffer.label = "MyCommand"
|
||||||
@ -240,28 +295,7 @@ public final class CompressedImageRenderer {
|
|||||||
|
|
||||||
let drawableSize = CGSize(width: CGFloat(outputTextures.textures[0].width), height: CGFloat(outputTextures.textures[0].height))
|
let drawableSize = CGSize(width: CGFloat(outputTextures.textures[0].width), height: CGFloat(outputTextures.textures[0].height))
|
||||||
|
|
||||||
var maybeDrawable: CAMetalDrawable?
|
guard let drawable = self.getNextDrawable(metalLayer: metalLayer, drawableSize: drawableSize) else {
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
if let metalLayer = metalLayer as? CAMetalLayer {
|
|
||||||
if metalLayer.drawableSize != drawableSize {
|
|
||||||
metalLayer.drawableSize = drawableSize
|
|
||||||
}
|
|
||||||
maybeDrawable = metalLayer.nextDrawable()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
preconditionFailure()
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if let metalLayer = metalLayer as? CAMetalLayer {
|
|
||||||
if metalLayer.drawableSize != drawableSize {
|
|
||||||
metalLayer.drawableSize = drawableSize
|
|
||||||
}
|
|
||||||
maybeDrawable = metalLayer.nextDrawable()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
guard let drawable = maybeDrawable else {
|
|
||||||
commandBuffer.commit()
|
commandBuffer.commit()
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
@ -287,13 +321,34 @@ public final class CompressedImageRenderer {
|
|||||||
|
|
||||||
renderEncoder.endEncoding()
|
renderEncoder.endEncoding()
|
||||||
|
|
||||||
commandBuffer.present(drawable)
|
var storedDrawable: MTLDrawable? = drawable
|
||||||
|
commandBuffer.addScheduledHandler { _ in
|
||||||
|
storedDrawable?.present()
|
||||||
|
storedDrawable = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
commandBuffer.addCompletedHandler { _ in
|
commandBuffer.addCompletedHandler { _ in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
if #available(iOS 10.3, *) {
|
||||||
|
drawable.addPresentedHandler { _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
commandBuffer.addCompletedHandler { _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
commandBuffer.commit()
|
commandBuffer.commit()
|
||||||
}
|
}
|
||||||
@ -316,8 +371,15 @@ public final class CompressedImageRenderer {
|
|||||||
rgbTexture = texture
|
rgbTexture = texture
|
||||||
}
|
}
|
||||||
|
|
||||||
rgbTexture.readDirect(width: width, height: height, bytesPerRow: bytesPerRow, read: { destination, maxLength in
|
rgbTexture.readDirect(width: width, height: height, bytesPerRow: bytesPerRow, read: { destinationBytes in
|
||||||
data.copyBytes(to: destination.assumingMemoryBound(to: UInt8.self), from: 0 ..< min(maxLength, data.count))
|
return data.withUnsafeBytes { bytes -> UnsafeRawPointer in
|
||||||
|
if let destinationBytes = destinationBytes {
|
||||||
|
memcpy(destinationBytes, bytes.baseAddress!, bytes.count)
|
||||||
|
return UnsafeRawPointer(destinationBytes)
|
||||||
|
} else {
|
||||||
|
return bytes.baseAddress!
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,35 +390,14 @@ public final class CompressedImageRenderer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let commandBuffer = self.shared.commandQueue.makeCommandBuffer() else {
|
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
commandBuffer.label = "MyCommand"
|
commandBuffer.label = "MyCommand"
|
||||||
|
|
||||||
let drawableSize = CGSize(width: CGFloat(rgbTexture.width), height: CGFloat(rgbTexture.height))
|
let drawableSize = CGSize(width: CGFloat(rgbTexture.width), height: CGFloat(rgbTexture.height))
|
||||||
|
|
||||||
var maybeDrawable: CAMetalDrawable?
|
guard let drawable = self.getNextDrawable(metalLayer: metalLayer, drawableSize: drawableSize) else {
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
if let metalLayer = metalLayer as? CAMetalLayer {
|
|
||||||
if metalLayer.drawableSize != drawableSize {
|
|
||||||
metalLayer.drawableSize = drawableSize
|
|
||||||
}
|
|
||||||
maybeDrawable = metalLayer.nextDrawable()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
preconditionFailure()
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if let metalLayer = metalLayer as? CAMetalLayer {
|
|
||||||
if metalLayer.drawableSize != drawableSize {
|
|
||||||
metalLayer.drawableSize = drawableSize
|
|
||||||
}
|
|
||||||
maybeDrawable = metalLayer.nextDrawable()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
guard let drawable = maybeDrawable else {
|
|
||||||
commandBuffer.commit()
|
commandBuffer.commit()
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
@ -413,7 +454,7 @@ public final class CompressedImageRenderer {
|
|||||||
pixelFormat: .rg8Unorm
|
pixelFormat: .rg8Unorm
|
||||||
),
|
),
|
||||||
TextureSet.Description(
|
TextureSet.Description(
|
||||||
fractionWidth: 1, fractionHeight: 1,
|
fractionWidth: 2, fractionHeight: 1,
|
||||||
pixelFormat: .r8Uint
|
pixelFormat: .r8Uint
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -431,42 +472,34 @@ public final class CompressedImageRenderer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
yuvaTextures.textures[0].readDirect(width: width, height: height, bytesPerRow: width, read: { destination, maxLength in
|
yuvaTextures.textures[0].readDirect(width: width, height: height, bytesPerRow: width, read: { destinationBytes in
|
||||||
memcpy(destination, yuva.advanced(by: 0), min(width * height, maxLength))
|
if let destinationBytes = destinationBytes {
|
||||||
|
memcpy(destinationBytes, yuva.advanced(by: 0), width * height)
|
||||||
|
return UnsafeRawPointer(destinationBytes)
|
||||||
|
} else {
|
||||||
|
return UnsafeRawPointer(yuva.advanced(by: 0))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
yuvaTextures.textures[1].readDirect(width: width / 2, height: height / 2, bytesPerRow: width, read: { destination, maxLength in
|
yuvaTextures.textures[1].readDirect(width: width / 2, height: height / 2, bytesPerRow: width, read: { destinationBytes in
|
||||||
memcpy(destination, yuva.advanced(by: width * height), min(width * height, maxLength))
|
if let destinationBytes = destinationBytes {
|
||||||
|
memcpy(destinationBytes, yuva.advanced(by: width * height), width * height / 2)
|
||||||
|
return UnsafeRawPointer(destinationBytes)
|
||||||
|
} else {
|
||||||
|
return UnsafeRawPointer(yuva.advanced(by: width * height))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var unpackedAlpha = Data(count: width * height)
|
yuvaTextures.textures[2].readDirect(width: width / 2, height: height, bytesPerRow: width / 2, read: { destinationBytes in
|
||||||
unpackedAlpha.withUnsafeMutableBytes { alphaBuffer in
|
if let destinationBytes = destinationBytes {
|
||||||
let alphaBytes = alphaBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
memcpy(destinationBytes, yuva.advanced(by: width * height * 2), width / 2 * height)
|
||||||
let alpha = yuva.advanced(by: width * height * 2)
|
return UnsafeRawPointer(destinationBytes)
|
||||||
|
} else {
|
||||||
var i = 0
|
return UnsafeRawPointer(yuva.advanced(by: width * height * 2))
|
||||||
for y in 0 ..< height {
|
|
||||||
let alphaRow = alphaBytes.advanced(by: y * width)
|
|
||||||
|
|
||||||
var x = 0
|
|
||||||
while x < width {
|
|
||||||
let a = alpha[i / 2]
|
|
||||||
let a1 = (a & (0xf0))
|
|
||||||
let a2 = ((a & (0x0f)) << 4)
|
|
||||||
alphaRow[x + 0] = a1 | (a1 >> 4);
|
|
||||||
alphaRow[x + 1] = a2 | (a2 >> 4);
|
|
||||||
|
|
||||||
x += 2
|
|
||||||
i += 2
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
yuvaTextures.textures[2].readDirect(width: width, height: height, bytesPerRow: width, read: { destination, maxLength in
|
|
||||||
memcpy(destination, alphaBytes, min(maxLength, width * height))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public func renderYuva(metalLayer: CALayer, width: Int, height: Int, data: Data, completion: @escaping () -> Void) {
|
public func renderYuva(metalLayer: CALayer, width: Int, height: Int, data: Data, completion: @escaping () -> Void) {
|
||||||
self.updateYuvaTextures(width: width, height: height, data: data)
|
self.updateYuvaTextures(width: width, height: height, data: data)
|
||||||
@ -475,35 +508,14 @@ public final class CompressedImageRenderer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let commandBuffer = self.shared.commandQueue.makeCommandBuffer() else {
|
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
commandBuffer.label = "MyCommand"
|
commandBuffer.label = "MyCommand"
|
||||||
|
|
||||||
let drawableSize = CGSize(width: CGFloat(yuvaTextures.width), height: CGFloat(yuvaTextures.height))
|
let drawableSize = CGSize(width: CGFloat(yuvaTextures.width), height: CGFloat(yuvaTextures.height))
|
||||||
|
|
||||||
var maybeDrawable: CAMetalDrawable?
|
guard let drawable = self.getNextDrawable(metalLayer: metalLayer, drawableSize: drawableSize) else {
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
if let metalLayer = metalLayer as? CAMetalLayer {
|
|
||||||
if metalLayer.drawableSize != drawableSize {
|
|
||||||
metalLayer.drawableSize = drawableSize
|
|
||||||
}
|
|
||||||
maybeDrawable = metalLayer.nextDrawable()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
preconditionFailure()
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if let metalLayer = metalLayer as? CAMetalLayer {
|
|
||||||
if metalLayer.drawableSize != drawableSize {
|
|
||||||
metalLayer.drawableSize = drawableSize
|
|
||||||
}
|
|
||||||
maybeDrawable = metalLayer.nextDrawable()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
guard let drawable = maybeDrawable else {
|
|
||||||
commandBuffer.commit()
|
commandBuffer.commit()
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
@ -524,8 +536,8 @@ public final class CompressedImageRenderer {
|
|||||||
renderEncoder.setFragmentTexture(yuvaTextures.textures[1].texture, index: 1)
|
renderEncoder.setFragmentTexture(yuvaTextures.textures[1].texture, index: 1)
|
||||||
renderEncoder.setFragmentTexture(yuvaTextures.textures[2].texture, index: 2)
|
renderEncoder.setFragmentTexture(yuvaTextures.textures[2].texture, index: 2)
|
||||||
|
|
||||||
var alphaWidth: Int32 = Int32(yuvaTextures.textures[2].texture.width)
|
var alphaSize = simd_uint2(UInt32(yuvaTextures.textures[0].texture.width), UInt32(yuvaTextures.textures[0].texture.height))
|
||||||
renderEncoder.setFragmentBytes(&alphaWidth, length: 4, index: 3)
|
renderEncoder.setFragmentBytes(&alphaSize, length: 8, index: 3)
|
||||||
|
|
||||||
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6)
|
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user