基于浮点数距离计算的绘图应用笔触渲染性能与精度问题
绘图应用浮点数计算性能与精度优化方案
针对你的两个技术问题,直接给出优化方案:
1. 浮点数距离计算下提升渲染性能
- 砍掉图层创建开销:你当前每次绘制都新建
CALayer,这是性能暴跌的核心原因——大量独立图层会导致GPU过度绘制、内存占用飙升。改用Core Graphics直接在画布批量绘制,或者预渲染brush到上下文,减少GPU指令调度成本。 - 简化距离计算:
isAdd函数里的类型转换可以提前完成,避免循环中重复转CGFloat;同时把空间阈值提前转为浮点数,减少函数调用时的运算开销。 - 优化插值逻辑:当前
interpolatePoints里的整数转换会丢失浮点数精度,还产生冗余点。直接基于浮点数插值,跳过重复坐标判断,减少数组元素数量。 - 严格控制绘制范围:利用
loopIndex只处理新增的点,不要每次遍历整个数组(你已经在做,但可以更严格地限制绘制区间)。
2. 不牺牲性能保证定位精准的替代方案
- Core Graphics批量绘制:放弃逐个添加
CALayer,改用CGContext一次性绘制所有brush点,GPU批量处理渲染指令,性能远高于多个独立图层。 - 整数运算兼容方案:如果必须保留浮点数精度,可以将所有坐标乘以固定倍数(比如100)转为整数运算,最后再除以倍数还原,既避免浮点数精度误差,又降低运算开销。
- 预计算缓存:提前缓存不同大小、角度、透明度的brush图像,绘制时直接取用,避免实时计算transform和opacity。
- Metal加速(高阶方案):对性能要求极高时,用Metal直接操作GPU,自定义渲染管线,比Core Graphics和CALayer效率更高,但实现成本稍高。
核心代码优化示例
优化isAdd距离判断函数
// 提前把space转为CGFloat,避免每次函数调用时的类型转换 func isAdd(p: CGPoint, space: CGFloat) -> Bool { guard let previousPoint = previousPoint else { return true } let dx = abs(previousPoint.x - p.x) let dy = abs(previousPoint.y - p.y) return max(dx, dy) >= space }
调用时直接传入CGFloat(getSpace()),减少类型转换开销。
优化插值逻辑,保留浮点数精度
func interpolatePoints(from array: [CGPoint]) -> [CGPoint] { var result: [CGPoint] = [] guard array.count >= 2 else { return array } for i in 0..<array.count-1 { let prev = array[i] let curr = array[i+1] let dx = curr.x - prev.x let dy = curr.y - prev.y let distance = hypot(dx, dy) guard distance > 0 else { continue } // 根据间隔计算需要插入的点数,直接生成精准浮点数坐标 let stepCount = max(1, Int(distance / CGFloat(getSpace()))) let stepX = dx / CGFloat(stepCount) let stepY = dy / CGFloat(stepCount) for j in 0...stepCount { let x = prev.x + stepX * CGFloat(j) let y = prev.y + stepY * CGFloat(j) result.append(CGPoint(x: x, y: y)) } } return result }
改用Core Graphics批量绘制(替代CALayer方案)
override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext(), pts2.count > 0 else { return } context.saveGState() context.setBlendMode(blendMode) let brushSize = shapeSize let halfWidth = brushSize.width / 2 let halfHeight = brushSize.height / 2 for idx in loopIndex..<pts2.count { let point = pts2[idx] guard isAdd(p: point, space: CGFloat(getSpace())) else { continue } previousPoint = point // 计算当前brush的缩放和透明度 let maxScale: CGFloat = 1.0 let s = 1.0 - CGFloat(dynamicScale) let scale = min(s + CGFloat(idx)/400.0, maxScale) let minOpacity = CGFloat(1.0 - dynamicOpacity) let opacity = min(minOpacity + CGFloat(idx)/300.0, brushAlpha) // 绘制brush context.saveGState() context.translateBy(x: point.x, y: point.y) context.scaleBy(x: scale, y: scale) // 转换角度为弧度 let angle = self.angle ? angles.randomElement()! : CGFloat(degree) context.rotate(by: angle * .pi / 180) context.setAlpha(opacity) shape.draw(in: CGRect(x: -halfWidth, y: -halfHeight, width: brushSize.width, height: brushSize.height)) context.restoreGState() loopIndex = idx } context.restoreGState() }
内容的提问来源于stack exchange,提问作者Rezwan Khan chowdhury
相关产品推荐
相关产品推荐

