Skip to content

Commit

Permalink
Fixed Bug In Frame
Browse files Browse the repository at this point in the history
  • Loading branch information
Lakr233 committed Aug 13, 2024
1 parent 93f8ef4 commit 66e2150
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 50 deletions.
8 changes: 4 additions & 4 deletions Example/ColorfulApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/* Begin PBXBuildFile section */
507C2F092C36FB1000BCB5FA /* ChessboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507C2F082C36FB1000BCB5FA /* ChessboardView.swift */; };
50C663882B19B04800AF7053 /* ColorfulAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C663872B19B04800AF7053 /* ColorfulAppApp.swift */; };
50C663882B19B04800AF7053 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C663872B19B04800AF7053 /* App.swift */; };
50C6638A2B19B04800AF7053 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C663892B19B04800AF7053 /* ContentView.swift */; };
50C6638C2B19B04900AF7053 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50C6638B2B19B04900AF7053 /* Assets.xcassets */; };
50C663982B19B07B00AF7053 /* ColorfulX in Frameworks */ = {isa = PBXBuildFile; productRef = 50C663972B19B07B00AF7053 /* ColorfulX */; };
Expand All @@ -17,7 +17,7 @@
/* Begin PBXFileReference section */
507C2F082C36FB1000BCB5FA /* ChessboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChessboardView.swift; sourceTree = "<group>"; };
50C663842B19B04800AF7053 /* ColorfulApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ColorfulApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
50C663872B19B04800AF7053 /* ColorfulAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorfulAppApp.swift; sourceTree = "<group>"; };
50C663872B19B04800AF7053 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
50C663892B19B04800AF7053 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
50C6638B2B19B04900AF7053 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
50C6638D2B19B04900AF7053 /* ColorfulApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ColorfulApp.entitlements; sourceTree = "<group>"; };
Expand Down Expand Up @@ -55,7 +55,7 @@
50C663862B19B04800AF7053 /* ColorfulApp */ = {
isa = PBXGroup;
children = (
50C663872B19B04800AF7053 /* ColorfulAppApp.swift */,
50C663872B19B04800AF7053 /* App.swift */,
50C663892B19B04800AF7053 /* ContentView.swift */,
507C2F082C36FB1000BCB5FA /* ChessboardView.swift */,
50C6638B2B19B04900AF7053 /* Assets.xcassets */,
Expand Down Expand Up @@ -145,7 +145,7 @@
files = (
50C6638A2B19B04800AF7053 /* ContentView.swift in Sources */,
507C2F092C36FB1000BCB5FA /* ChessboardView.swift in Sources */,
50C663882B19B04800AF7053 /* ColorfulAppApp.swift in Sources */,
50C663882B19B04800AF7053 /* App.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
showGraphicsOverview = "Yes"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// ColorfulAppApp.swift
// App.swift
// ColorfulApp
//
// Created by QAQ on 2023/12/1.
Expand Down
13 changes: 7 additions & 6 deletions Example/ColorfulApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct ContentView: View {
bias: $bias,
noise: $noise,
transitionSpeed: $duration,
frameLimit: 45,
renderScale: .init(scale)
)
.background(ChessboardView().opacity(0.25))
Expand Down Expand Up @@ -97,7 +98,7 @@ struct ContentView: View {
Spacer()
Text("\(speed, specifier: "%.1f")")
}
#if !os(tvOS)
#if os(iOS)
Slider(value: $speed, in: 0.0 ... 10.0, step: 0.1) { _ in
}
#endif
Expand All @@ -110,7 +111,7 @@ struct ContentView: View {
Spacer()
Text("\(bias, specifier: "%.5f")")
}
#if !os(tvOS)
#if os(iOS)
Slider(value: $bias, in: 0.00001 ... 0.01, step: 0.00001) { _ in
}
#endif
Expand All @@ -123,7 +124,7 @@ struct ContentView: View {
Spacer()
Text("\(noise, specifier: "%.2f")")
}
#if !os(tvOS)
#if os(iOS)
Slider(value: $noise, in: 0 ... 64, step: 1) { _ in
}
#endif
Expand All @@ -136,7 +137,7 @@ struct ContentView: View {
Spacer()
Text("\(duration, specifier: "%.2f")")
}
#if !os(tvOS)
#if os(iOS)
Slider(value: $duration, in: 0.0 ... 10.0, step: 0.1) { _ in
}
#endif
Expand All @@ -149,8 +150,8 @@ struct ContentView: View {
Spacer()
Text("\(scale, specifier: "%.4f")")
}
#if !os(tvOS)
Slider(value: $scale, in: 0.0001 ... 2.0, step: 0.0001) { _ in
#if os(iOS)
Slider(value: $scale, in: 0.001 ... 2.0, step: 0.001) { _ in
}
#endif
}
Expand Down
13 changes: 3 additions & 10 deletions Sources/ColorfulX/AnimatedMulticolorGradientView+Update.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,9 @@ extension AnimatedMulticolorGradientView {
}
}

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)
func updateRenderParameters(deltaTime: Double) {
// clear the flag
defer { renderInputWasModified = false }

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

Expand Down
105 changes: 82 additions & 23 deletions Sources/ColorfulX/AnimatedMulticolorGradientView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,43 @@ private let SPRING_CONFIG = SpringInterpolation.Configuration(
dampingRatio: 0.2
)
private let SPRING_ENGINE = SpringInterpolation2D(SPRING_CONFIG)
private let defaultFrameRate: Int = 60

open class AnimatedMulticolorGradientView: MulticolorGradientView {
public var lastUpdate: Double = 0
public var lastRender: Double = 0
public var needsUpdateRenderParameters: Bool = false
// MARK: - PROPERTY

public private(set) var lastRenderParametersUpdate: Double = 0
public private(set) var lastRenderExecution: Double = 0
public var renderInputWasModified: Bool = false {
didSet { lastRenderParametersUpdate = obtainCurrentTimestamp() }
}

public internal(set) var colorElements: [Speckle] {
didSet { needsUpdateRenderParameters = true }
didSet { renderInputWasModified = true }
}

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

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

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

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

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

// MARK: - FUNCTION

override public init() {
colorElements = .init(repeating: .init(position: SPRING_ENGINE), count: Uniforms.COLOR_SLOT)
super.init()
Expand Down Expand Up @@ -72,25 +79,77 @@ open class AnimatedMulticolorGradientView: MulticolorGradientView {
}
}

// MARK: - GETTER

@inline(__always)
func obtainCurrentTimestamp() -> Double { CACurrentMediaTime() }

@inline(__always)
func frameLimiterShouldScheduleNextFrame() -> Bool {
guard frameLimit > 0 else { return true }

let currentTime = obtainCurrentTimestamp()
let deltaTime = currentTime - lastRenderExecution
let requiredDeltaTime = 1.0 / Double(frameLimit)

let nextTierFrameRate = Double(frameLimit * 2)
let nextTierDeltaTime = 1.0 / nextTierFrameRate

let decisionDeltaTime = requiredDeltaTime - nextTierDeltaTime

return deltaTime >= decisionDeltaTime

/*
we are not dead loop here so vsync already delays for 16ms in 60hz display
if we give and one frame each time, there would be 30 fps to actually draw on display

based on this fact, frame limit can be 7, 15, 30, 60, 120...
requiredDeltaTime needs to shift in order to comply with our goal
*/
}

@inline(__always)
private func deltaTimeForRenderParametersUpdate() -> Double {
let currentTime = obtainCurrentTimestamp()
let realDeltaTime = currentTime - lastRenderParametersUpdate
var frameRate = frameLimit
if frameRate < 1 { frameRate = defaultFrameRate }
let maxAllowedDeltaTime = 1.0 / Double(frameRate)
if realDeltaTime > maxAllowedDeltaTime { return maxAllowedDeltaTime }
return realDeltaTime
}

// MARK: - RENDER LIFE CYCLE

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

// skip any vsync check and force an update
renderInputWasModified = true
updateRenderParameters(deltaTime: deltaTimeForRenderParametersUpdate())
renderIfNeeded()
}

override func renderIfNeeded() {
super.renderIfNeeded()
}

override func render() {
super.render()
lastRenderExecution = obtainCurrentTimestamp()
}

override func vsync() {
defer { super.vsync() }
guard needsUpdateRenderParameters || speed > 0 else { return }
defer { self.updateRenderParameters() }
guard frameLimit > 0 else { return }

var decisionFrameRate = frameLimit - 1
if decisionFrameRate < 1 { decisionFrameRate = 1 }
let wantedDeltaTime = 1.0 / Double(decisionFrameRate)
let now = CACurrentMediaTime()
guard now - lastUpdate >= wantedDeltaTime else { return }
lastRender = now
// check if we should render
guard frameLimiterShouldScheduleNextFrame() else { return }

// check if we need to update the render parameter
// the underlying MulticolorGradientView will only render if parameter was modified
guard speed > 0 || renderInputWasModified else { return }
updateRenderParameters(deltaTime: deltaTimeForRenderParametersUpdate())

// sine the render parameters were updated, we call super.vsync to render
super.vsync()
}

func computeSpeckleColor(_ speckle: Speckle) -> ColorVector {
Expand Down
2 changes: 1 addition & 1 deletion Sources/ColorfulX/DisplayLinkDriver+CV.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ import Foundation
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
guard let displayLink else { return }
CVDisplayLinkSetOutputCallback(displayLink, { _, _, _, _, _, _ -> CVReturn in
CVDisplayLinkDriverHelper.dispatchUpdate()
autoreleasepool { CVDisplayLinkDriverHelper.dispatchUpdate() }
return kCVReturnSuccess
}, nil)
CVDisplayLinkStart(displayLink)
Expand Down
7 changes: 6 additions & 1 deletion Sources/ColorfulX/MetalLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class MetalLink: DisplayLinkDelegate {
var onSynchronizationUpdate: SynchornizationUpdate?

var scaleFactor: Double = 1.0 {
didSet { updateDrawableSize(withBounds: metalLayer.bounds) }
didSet { updateDrawableSizeFromFrame() }
}

enum MetalError: Error {
Expand Down Expand Up @@ -48,7 +48,12 @@ class MetalLink: DisplayLinkDelegate {
}

func updateDrawableSize(withBounds bounds: CGRect) {
guard metalLayer.frame != bounds else { return }
metalLayer.frame = bounds
}

func updateDrawableSizeFromFrame() {
let bounds = metalLayer.bounds
var width = bounds.width * scaleFactor
var height = bounds.height * scaleFactor
if width <= 0 { width = 1 }
Expand Down
12 changes: 9 additions & 3 deletions Sources/ColorfulX/MulticolorGradientView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,18 @@ open class MulticolorGradientView: MetalView {

override func vsync() {
super.vsync()
renderIfNeeded()
}

guard lock.try() else { return }
defer { lock.unlock() }

func renderIfNeeded() {
guard needsRender else { return }
defer { needsRender = false }
render()
}

func render() {
guard lock.try() else { return }
defer { lock.unlock() }

guard let metalLink,
let computePipelineState
Expand Down

0 comments on commit 66e2150

Please sign in to comment.