Skip to content

Commit

Permalink
Reduce Drawing @ Speed = 0
Browse files Browse the repository at this point in the history
  • Loading branch information
Lakr233 committed Aug 12, 2024
1 parent 7ad28e1 commit 777230b
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 81 deletions.
76 changes: 76 additions & 0 deletions Sources/ColorfulX/AnimatedMulticolorGradientView+Update.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// AnimatedMulticolorGradientView+Update.swift
//
//
// Created by 秋星桥 on 2024/8/12.
//

import Foundation

extension AnimatedMulticolorGradientView {
private func randomLocationPair() -> (x: Double, y: Double) {
(
x: Double.random(in: 0 ... 1),
y: Double.random(in: 0 ... 1)
)
}

func initializeRenderParameters() {
var rand = randomLocationPair()
for idx in 0 ..< colorElements.count {
rand = randomLocationPair()
colorElements[idx].position.setCurrent(.init(x: rand.x, y: rand.y))
rand = randomLocationPair()
colorElements[idx].position.setTarget(.init(x: rand.x, y: rand.y))
}
}

func updateRenderParameters() {
defer { needsUpdateRenderParameters = false }

var deltaTime = -Date(timeIntervalSince1970: lastUpdate).timeIntervalSinceNow
lastUpdate = Date().timeIntervalSince1970
guard deltaTime > 0 else { return }

// when the app goes back from background, deltaTime could be very large
let maxDeltaAllowed = 1.0 / Double(frameLimit > 0 ? frameLimit : 30)
deltaTime = min(deltaTime, maxDeltaAllowed)

let moveDelta = deltaTime * speed * 0.5 // just slow down

for idx in 0 ..< colorElements.count where colorElements[idx].enabled {
var inplaceEdit = colorElements[idx]
defer { colorElements[idx] = inplaceEdit }

if inplaceEdit.transitionProgress.context.currentPos < 1 {
inplaceEdit.transitionProgress.update(withDeltaTime: deltaTime * transitionSpeed)
}
if moveDelta > 0 {
inplaceEdit.position.update(withDeltaTime: moveDelta)

let pos_x = inplaceEdit.position.x.context.currentPos
let tar_x = inplaceEdit.position.x.context.targetPos
let pos_y = inplaceEdit.position.y.context.currentPos
let tar_y = inplaceEdit.position.y.context.targetPos
if abs(pos_x - tar_x) < 0.125 || abs(pos_y - tar_y) < 0.125 {
let rand = randomLocationPair()
inplaceEdit.position.setTarget(.init(x: rand.x, y: rand.y))
}
}
}

parameters = .init(
points: colorElements
.filter(\.enabled)
.map { .init(
color: computeSpeckleColor($0),
position: .init(
x: $0.position.x.context.currentPos,
y: $0.position.y.context.currentPos
)
) },
bias: bias,
noise: noise
)
}
}
113 changes: 40 additions & 73 deletions Sources/ColorfulX/AnimatedMulticolorGradientView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,38 @@ private let SPRING_ENGINE = SpringInterpolation2D(SPRING_CONFIG)
open class AnimatedMulticolorGradientView: MulticolorGradientView {
public var lastUpdate: Double = 0
public var lastRender: Double = 0
public private(set) var colorElements: [Speckle]
public var needsUpdateRenderParameters: Bool = false

public var speed: Double = 1.0
public var bias: Double = 0.01
public var noise: Double = 0
public var transitionSpeed: Double = 1
public var frameLimit: Int = 0
public internal(set) var colorElements: [Speckle] {
didSet { needsUpdateRenderParameters = true }
}

public var speed: Double = 1.0 {
didSet { needsUpdateRenderParameters = true }
}

public var bias: Double = 0.01 {
didSet { needsUpdateRenderParameters = true }
}

public var noise: Double = 0 {
didSet { needsUpdateRenderParameters = true }
}

public var transitionSpeed: Double = 1 {
didSet { needsUpdateRenderParameters = true }
}

public var frameLimit: Int = 0 {
didSet { needsUpdateRenderParameters = true }
}

override public init() {
colorElements = .init(repeating: .init(position: SPRING_ENGINE), count: Uniforms.COLOR_SLOT)

super.init()

var rand = randomLocationPair()
for idx in 0 ..< colorElements.count {
rand = randomLocationPair()
colorElements[idx].position.setCurrent(.init(x: rand.x, y: rand.y))
rand = randomLocationPair()
colorElements[idx].position.setTarget(.init(x: rand.x, y: rand.y))
}
initializeRenderParameters()

#if canImport(UIKit)
NotificationCenter.default.addObserver(
Expand All @@ -49,24 +61,24 @@ open class AnimatedMulticolorGradientView: MulticolorGradientView {
#endif
}

@objc
func applicationWillEnterForeground(_: Notification) {
lastRender = .init()
}
#if canImport(UIKit)
@objc
func applicationWillEnterForeground(_: Notification) {
lastRender = .init()
needsUpdateRenderParameters = true
}
#endif

deinit {
#if canImport(UIKit)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.removeObserver(
self,
name: UIApplication.willEnterForegroundNotification,
object: nil
)
#endif
}

private func randomLocationPair() -> (x: Double, y: Double) {
(
x: Double.random(in: 0 ... 1),
y: Double.random(in: 0 ... 1)
)
}

public func setColors(_ colors: [ColorVector], interpolationEnabled: Bool = true) {
var colors = colors
if colors.isEmpty { colors.append(.init(v: .zero, space: .rgb)) }
Expand All @@ -86,60 +98,16 @@ open class AnimatedMulticolorGradientView: MulticolorGradientView {
}
}

private func updateRenderParameters() {
var deltaTime = -Date(timeIntervalSince1970: lastUpdate).timeIntervalSinceNow
lastUpdate = Date().timeIntervalSince1970
guard deltaTime > 0 else { return }

// when the app goes back from background, deltaTime could be very large
let maxDeltaAllowed = 1.0 / Double(frameLimit > 0 ? frameLimit : 30)
deltaTime = min(deltaTime, maxDeltaAllowed)

let moveDelta = deltaTime * speed * 0.5 // just slow down

for idx in 0 ..< colorElements.count where colorElements[idx].enabled {
var inplaceEdit = colorElements[idx]
defer { colorElements[idx] = inplaceEdit }

if inplaceEdit.transitionProgress.context.currentPos < 1 {
inplaceEdit.transitionProgress.update(withDeltaTime: deltaTime * transitionSpeed)
}
if moveDelta > 0 {
inplaceEdit.position.update(withDeltaTime: moveDelta)

let pos_x = inplaceEdit.position.x.context.currentPos
let tar_x = inplaceEdit.position.x.context.targetPos
let pos_y = inplaceEdit.position.y.context.currentPos
let tar_y = inplaceEdit.position.y.context.targetPos
if abs(pos_x - tar_x) < 0.125 || abs(pos_y - tar_y) < 0.125 {
let rand = randomLocationPair()
inplaceEdit.position.setTarget(.init(x: rand.x, y: rand.y))
}
}
}

parameters = .init(
points: colorElements
.filter(\.enabled)
.map { .init(
color: computeSpeckleColor($0),
position: .init(
x: $0.position.x.context.currentPos,
y: $0.position.y.context.currentPos
)
) },
bias: bias,
noise: noise
)
}

override public func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
needsUpdateRenderParameters = true
updateRenderParameters()
super.vsync()
}

override func vsync() {
defer { super.vsync() }
guard needsUpdateRenderParameters || speed > 0 else { return }
// when calling from vsync, MetalView is holding strong reference.
DispatchQueue.main.asyncAndWait(execute: DispatchWorkItem {
if self.frameLimit > 0 {
Expand All @@ -149,7 +117,6 @@ open class AnimatedMulticolorGradientView: MulticolorGradientView {
}
self.updateRenderParameters()
})
super.vsync()
}

func computeSpeckleColor(_ speckle: Speckle) -> ColorVector {
Expand Down
8 changes: 4 additions & 4 deletions Sources/ColorfulX/ColorfulPreset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ public enum ColorfulPreset: String, CaseIterable {
case .neon: return [make(22, 4, 74), make(240, 54, 248), make(79, 216, 248), make(74, 0, 217)]
case .aurora: return [make(0, 209, 172), make(0, 150, 150), make(4, 76, 112), make(23, 38, 69)]
case .appleIntelligence: return [
make(239, 176, 76), make(233, 128, 86), make(234, 75, 107),
make(230, 97, 165), make(223, 138, 233), make(192, 160, 245),
make(100, 181, 245), make(126, 201, 238)
].map { ColorElement($0) }.map { $0.withAlphaComponent(.random(in: 0.75 ... 1.0)) }.map { Color($0) }
make(239, 176, 76), make(233, 128, 86), make(234, 75, 107),
make(230, 97, 165), make(223, 138, 233), make(192, 160, 245),
make(100, 181, 245), make(126, 201, 238),
].map { ColorElement($0) }.map { $0.withAlphaComponent(.random(in: 0.75 ... 1.0)) }.map { Color($0) }
case .colorful: return [#colorLiteral(red: 0.9586862922, green: 0.660125792, blue: 0.8447988033, alpha: 1), #colorLiteral(red: 0.8714533448, green: 0.723166883, blue: 0.9342088699, alpha: 1), #colorLiteral(red: 0.7458761334, green: 0.7851135731, blue: 0.9899476171, alpha: 1), #colorLiteral(red: 0.4398113191, green: 0.8953480721, blue: 0.9796616435, alpha: 1), #colorLiteral(red: 0.3484552801, green: 0.933657825, blue: 0.9058339596, alpha: 1), #colorLiteral(red: 0.5567936897, green: 0.9780793786, blue: 0.6893508434, alpha: 1)].map { .init($0.withAlphaComponent(0.5)) }
}
}
Expand Down
5 changes: 3 additions & 2 deletions Sources/ColorfulX/MetalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import MetalKit
let metalLayer: CAMetalLayer
let commandQueue: MTLCommandQueue

var scaleFactor: Double = 1.0

private weak var mDisplayLink: CADisplayLink?
private var hasParentWindow: Bool = false
private var hasActiveScene: Bool = true
Expand Down Expand Up @@ -114,7 +116,6 @@ import MetalKit
// 15.79ms for a 1290x2796 image on iPhone 15 Pro Max
// native scaleFactor will case a performance issue
// so we downscale the image to 1x
let scaleFactor = 1.0
metalLayer.frame = bounds
var width = bounds.width * scaleFactor
var height = bounds.height * scaleFactor
Expand Down Expand Up @@ -208,6 +209,7 @@ import MetalKit
let commandQueue: MTLCommandQueue

var displayLink: CVDisplayLink?
var scaleFactor: Double = 1.0

init() {
guard let device = MTLCreateSystemDefaultDevice(),
Expand Down Expand Up @@ -253,7 +255,6 @@ import MetalKit
public func layoutSublayers(of layer: CALayer) {
guard layer == metalLayer else { return }
metalLayer.frame = bounds
let scaleFactor = 1.0
var width = bounds.width * scaleFactor
var height = bounds.height * scaleFactor
if width <= 0 { width = 1 }
Expand Down
5 changes: 3 additions & 2 deletions Sources/ColorfulX/MulticolorGradientView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ open class MulticolorGradientView: MetalView {
guard lock.try() else { return }
defer { lock.unlock() }

guard needsRender else { return }
defer { needsRender = false }

guard let drawable = metalLayer.nextDrawable(),
let commandBuffer = commandQueue.makeCommandBuffer(),
let commandEncoder = commandBuffer.makeComputeCommandEncoder()
else { return }

defer { needsRender = false }

var shaderPoints: [(simd_float2, simd_float4)] = Array(
repeating: (
simd_float2(0.0, 0.0),
Expand Down

0 comments on commit 777230b

Please sign in to comment.