如何使用 SwiftUI 连续渲染软件位图?
Posted
技术标签:
【中文标题】如何使用 SwiftUI 连续渲染软件位图?【英文标题】:How to continuously render a software bitmap with SwiftUI? 【发布时间】:2021-03-16 23:09:51 【问题描述】:我正在尝试为 MacOS 制作一个简单的软件渲染器,但无法连续显示我的位图。 The examples I 发现有关设置是使用 ios 或 OpenGL/Metal 视图。
这是我在 mcve 中的尝试,它编译但在几次迭代后停止更新屏幕。我已确保正确调用了 displayCallback
。
import SwiftUI
@main
struct PlatformApp: App
var body: some Scene
WindowGroup
ContentView()
public struct Color
public var r, g, b, a: UInt8
public init(r: UInt8, g: UInt8, b: UInt8, a: UInt8 = 255)
self.r = r
self.g = g
self.b = b
self.a = a
public extension Color
static let clear = Color(r: 0, g: 0, b: 0, a: 0)
static let red = Color(r: 255, g: 0, b: 0)
static let green = Color(r: 0, g: 255, b: 0)
static let blue = Color(r: 0, g: 0, b: 255)
extension Image
init?(bitmap: Bitmap)
let alphaInfo = CGImageAlphaInfo.premultipliedLast
let bytesPerPixel = MemoryLayout<Color>.size
let bytesPerRow = bitmap.width * bytesPerPixel
guard let providerRef = CGDataProvider(data: Data(
bytes: bitmap.pixels, count: bitmap.height * bytesPerRow
) as CFData) else
return nil
guard let cgImage = CGImage(
width: bitmap.width,
height: bitmap.height,
bitsPerComponent: 8,
bitsPerPixel: bytesPerPixel * 8,
bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGBitmapInfo(rawValue: alphaInfo.rawValue),
provider: providerRef,
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent
) else
return nil
self.init(decorative: cgImage, scale: 1.0, orientation: .up)
public struct Bitmap
public private(set) var pixels: [Color]
public let width: Int
public init(width: Int, pixels: [Color])
self.width = width
self.pixels = pixels
public extension Bitmap
var height: Int
return pixels.count / width
subscript(x: Int, y: Int) -> Color
get return pixels[y * width + x]
set pixels[y * width + x] = newValue
init(width: Int, height: Int, color: Color)
self.pixels = Array(repeating: color, count: width * height)
self.width = width
public struct Renderer
public private(set) var bitmap: Bitmap
private var i: Int = 0
private var j: Int = 0
public init(width: Int, height: Int, backgroundColor: Color = .clear)
self.bitmap = Bitmap(width: width, height: height, color: backgroundColor)
public extension Renderer
mutating func draw() // Temporary for testing
bitmap[i, j] = [Color.red, Color.green, Color.blue][i % 3]
i = i + 1
if (i >= bitmap.width)
i = 0
j += 1
if (j >= bitmap.height)
j = 0
struct ContentView: View
@State var game = Game(width: 10, height: 10)
var body: some View
game.image?
.resizable()
.interpolation(.none)
.frame(width: 200, height: 200, alignment: .center)
.aspectRatio(contentMode: .fit)
class Game
public var image: Image?
private var renderer: Renderer
private var displayLink: CVDisplayLink?
let displayCallback: CVDisplayLinkOutputCallback = displayLink, inNow, inOutputTime, flagsIn, flagsOut, displayLinkContext in
let game = unsafeBitCast(displayLinkContext, to: Game.self)
game.renderer.draw()
game.image = Image(bitmap: game.renderer.bitmap)
return kCVReturnSuccess
init(width: Int, height: Int)
self.renderer = Renderer(width: width, height: height)
let error = CVDisplayLinkCreateWithActiveCGDisplays(&self.displayLink)
guard let link = self.displayLink, kCVReturnSuccess == error else
NSLog("Display Link created with error: %d", error)
return
CVDisplayLinkSetOutputCallback(link, displayCallback, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
CVDisplayLinkStart(link)
deinit
CVDisplayLinkStop(self.displayLink!)
结果:
所以问题似乎是视图没有正确更新。我认为@State var game
会做到这一点,因此只要修改了视图就会更新,但我想我需要直接修改该字段才能注册。
我尝试在ContentView
中使用Timer
来调用更新它的函数,但我无法在其init
(Escaping closure captures mutating 'self' parameter
) 中捕获self
或在其中包含@objc
函数视图 (@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes
)。
我不确定如何解决这个问题。我尝试将ContentView
传递给CVDisplayLinkOutputCallback
,但我收到一条错误消息,指出Generic struct 'Unmanaged' requires that 'ContentView' be a class type
。
那么,如何使用 SwiftUI 持续渲染软件位图?
【问题讨论】:
【参考方案1】:您正在使用@State,但您的游戏是引用类型类而不是值类型结构。使用类似observedobject的东西
例如:快速而肮脏的 hack:
struct ContentView: View
@ObservedObject
var game = Game(width: 10, height: 10)
var body: some View
game.image?
.resizable()
.interpolation(.none)
.frame(width: 200, height: 200, alignment: .center)
.aspectRatio(contentMode: .fit)
class Game: ObservableObject
@Published
var toggle: Bool = false
public var image: Image?
didSet
DispatchQueue.main.async [weak self] in
self?.toggle.toggle()
【讨论】:
这将如何工作? ObservedObject 将发布更改,这是后台线程所不允许的。 我添加了一个示例 - 可能是一种糟糕的方法,但它似乎可以绘制以上是关于如何使用 SwiftUI 连续渲染软件位图?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Direct2D 从 BITMAPINFOHEADER 和 BYTE 渲染位图