SaganRitual
SaganRitual

Reputation: 3203

MacOS Metal app works in Obj-C, fails to draw in Swift translation

I've found a working example of a Metal app for macOS, written in Objective-C. It shows how to work with multiple GPUs, and it draws some pretty things too. I've pared it down quite a bit to get the most minimal example I can. It's here, as is my (attempted) translation to Swift.

Although the code is a bit involved, I think the problem is in a fairly contained place: drawing to the view/window/screen or whatever. Everything seems to be working perfectly except for the draw itself. I am guessing that anyone with some intuitions about the way views/windows/etc work could look at it for five seconds and know what's going wrong. One can always hope for the best.

I've been over and over the code, and I've tried countless permutations based on the documentation I've been able to find, but when it comes to views and that stuff, I don't even know what questions to ask. The GPU frame capture shows a perfect image on my pared-down Obj-C version, but it shows solid black on my Swift version, and I've not been able to find any reason for it. Putting out a call to view-friendly folks, cheers

Upvotes: 2

Views: 99

Answers (1)

TylerP
TylerP

Reputation: 9829

Turns out you had nothing wrong with the view drawing stuff, and instead had some problems with working with UnsafeMutablePointer.

In FoilRenderer.updateState, you have this:

var u = uniforms.assumingMemoryBound(to: FoilUniforms.self).pointee

u.pointSize = FoilRenderer.bodyPointSize
u.mvpMatrix = projectionMatrix

Since FoilUniforms is a value type, the pointee property stores a copy of the value into u. So when you change u's pointSize and mvpMatrix, you're not actually changing the memory in uniforms like you think you are. To fix this, assign the value back to pointee:

var u = uniforms.assumingMemoryBound(to: FoilUniforms.self).pointee

u.pointSize = FoilRenderer.bodyPointSize
u.mvpMatrix = projectionMatrix

uniforms.assumingMemoryBound(to: FoilUniforms.self).pointee = u

In this case, since we're setting both pointSize and mvpMatrix (the only two properties of a FoilUniforms struct), this can be simplified to the following:

let u = FoilUniforms(mvpMatrix: projectionMatrix,
                     pointSize: FoilRenderer.bodyPointSize)
uniforms.assumingMemoryBound(to: FoilUniforms.self).pointee = u

Similarly, you'll need to update a few places in FoilSimulation for the same reason:

Change:

var c = c_.assumingMemoryBound(to: FoilSimParams.self).pointee
c.timestep = config.simInterval
c.damping = config.damping
c.softeningSqr = config.softeningSqr
c.numBodies = UInt32(config.numBodies)

to:

let c = FoilSimParams(timestep: config.simInterval,
                      damping: config.damping,
                      softeningSqr: config.softeningSqr,
                      numBodies: UInt32(config.numBodies))

c_.assumingMemoryBound(to: FoilSimParams.self).pointee = c

and change:

var p = positions[i]
p.x = position.x; p.y = position.x; p.z = position.z
p.w = 1

to:

var p = positions[i]
p.x = position.x; p.y = position.y; p.z = position.z
p.w = 1

positions[i] = p

(Notice there's also another unrelated error here, where you have p.y = position.x – make sure to change that to p.y = position.y or else you get some weird looking simulations!)

And then finally, change:

var v = velocities[i]
v.x = velocity.x * vscale
v.y = velocity.y * vscale
v.z = velocity.z * vscale

to:

var v = velocities[i]
v.x = velocity.x * vscale
v.y = velocity.y * vscale
v.z = velocity.z * vscale

velocities[i] = v

After making just those changes, I was able to get it to render properly:

Foil renderer working

Upvotes: 2

Related Questions