Skip to content

Commit

Permalink
chore: FilterChain cleanup to prepare for removing PixelBuffer
Browse files Browse the repository at this point in the history
PixelBuffer will become one of two possible inputs to be processed.
Those being either

1. PixelBuffer; or
2. MTLTexture
  • Loading branch information
stuartcarnie committed Jul 21, 2022
1 parent 6d18a65 commit 728c7d2
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 83 deletions.
57 changes: 31 additions & 26 deletions OpenEmuShadersTests/SlangShaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,11 @@ shader0 = mem:///root/foo.slang
let pass = ss.passes[0]
XCTAssertEqual(pass.url, URL(string: "mem:///root/foo.slang")!)
XCTAssertEqual(pass.frameCountMod, 0)
XCTAssertEqual(pass.scaleX, .invalid)
XCTAssertEqual(pass.scaleY, .invalid)
XCTAssertNil(pass.scaleX)
XCTAssertNil(pass.scaleY)
XCTAssertEqual(pass.format, .bgra8Unorm)
XCTAssertEqual(pass.filter, .unspecified)
XCTAssertEqual(pass.wrapMode, .default)
XCTAssertEqual(pass.scale, .one)
XCTAssertEqual(pass.size, .zero)
XCTAssertFalse(pass.isScaled)
XCTAssertFalse(pass.isFloat)
XCTAssertFalse(pass.issRGB)
XCTAssertFalse(pass.isMipmap)
Expand Down Expand Up @@ -155,7 +152,7 @@ frame_count_mod2 = 100
func testShaderPassScale() {
let cfg =
"""
shaders = 13
shaders = 14
# shader zero verifies defaults
shader0 = mem:///root/foo.slang
Expand All @@ -173,6 +170,7 @@ scale_y2 = 200
shader3 = mem:///root/foo.slang
scale_type_x3 = absolute
scale_x3 = 100
scale_type_y3 = source
# source
Expand All @@ -187,6 +185,7 @@ scale_y5 = 0.55
shader6 = mem:///root/foo.slang
scale_type_x6 = source
scale_x6 = 2.5
scale_type_y6 = source
# viewport
Expand All @@ -201,6 +200,7 @@ scale_y8 = 0.55
shader9 = mem:///root/foo.slang
scale_type_x9 = viewport
scale_x9 = 2.5
scale_type_y9 = source
# scale
Expand All @@ -216,6 +216,14 @@ shader12 = mem:///root/foo.slang
scale_type12 = viewport
scale12 = 1.50
# invalid
# invalid because it only specifies one axis
shader13 = mem:///root/foo.slang
scale_type_x13 = viewport
scale_x13 = 1.50
"""

InMemProtocol.requests = [
Expand All @@ -230,46 +238,43 @@ scale12 = 1.50
do {
// test default
let passes = ss.passes[0...0]
XCTAssertEqual(passes.map(\.scaleX), [.invalid])
XCTAssertEqual(passes.map(\.scaleY), [.invalid])
XCTAssertEqual(passes.map(\.scale), [.one])
XCTAssertEqual(passes.map(\.size), [.zero])
XCTAssertEqual(passes.compactMap(\.scaleX), [])
XCTAssertEqual(passes.compactMap(\.scaleY), [])
}

do {
// test passes using absolute
let passes = ss.passes[1...3]
XCTAssertEqual(passes.map(\.scaleX), [.absolute, .absolute, .absolute])
XCTAssertEqual(passes.map(\.scaleY), [.absolute, .absolute, .source])
XCTAssertEqual(passes.map(\.scale), [.one, .one, .one])
XCTAssertEqual(passes.map(\.size), [.zero, CGSize(width: 100, height: 200), CGSize(width: 100, height: 0)])
XCTAssertEqual(passes.compactMap(\.scaleX), [.absolute(size: 0), .absolute(size: 100), .absolute(size: 100)])
XCTAssertEqual(passes.compactMap(\.scaleY), [.absolute(size: 0), .absolute(size: 200), .source(scale: 1)])
}

do {
// test passes using source
let passes = ss.passes[4...6]
XCTAssertEqual(passes.map(\.scaleX), [.source, .source, .source])
XCTAssertEqual(passes.map(\.scaleY), [.source, .source, .source])
XCTAssertEqual(passes.map(\.scale), [.one, CGSize(width: 0.25, height: 0.55), CGSize(width: 2.5, height: 1)])
XCTAssertEqual(passes.map(\.size), [.zero, .zero, .zero])
XCTAssertEqual(passes.compactMap(\.scaleX), [.source(scale: 1), .source(scale: 0.25), .source(scale: 2.5)])
XCTAssertEqual(passes.compactMap(\.scaleY), [.source(scale: 1), .source(scale: 0.55), .source(scale: 1.0)])
}

do {
// test passes using viewport
let passes = ss.passes[7...9]
XCTAssertEqual(passes.map(\.scaleX), [.viewport, .viewport, .viewport])
XCTAssertEqual(passes.map(\.scaleY), [.viewport, .viewport, .source])
XCTAssertEqual(passes.map(\.scale), [.one, CGSize(width: 0.25, height: 0.55), CGSize(width: 2.5, height: 1)])
XCTAssertEqual(passes.map(\.size), [.zero, .zero, .zero])
XCTAssertEqual(passes.compactMap(\.scaleX), [.viewport(scale: 1), .viewport(scale: 0.25), .viewport(scale: 2.5)])
XCTAssertEqual(passes.compactMap(\.scaleY), [.viewport(scale: 1), .viewport(scale: 0.55), .source(scale: 1.0)])
}

do {
// test passes using single scale scalar
let passes = ss.passes[10...12]
XCTAssertEqual(passes.map(\.scaleX), [.absolute, .source, .viewport])
XCTAssertEqual(passes.map(\.scaleY), [.absolute, .source, .viewport])
XCTAssertEqual(passes.map(\.scale), [.one, CGSize(width: 0.75, height: 0.75), CGSize(width: 1.50, height: 1.50)])
XCTAssertEqual(passes.map(\.size), [CGSize(width: 150, height: 150), .zero, .zero])
XCTAssertEqual(passes.compactMap(\.scaleX), [.absolute(size: 150), .source(scale: 0.75), .viewport(scale: 1.50)])
XCTAssertEqual(passes.compactMap(\.scaleY), [.absolute(size: 150), .source(scale: 0.75), .viewport(scale: 1.50)])
}

do {
// test with invalid scale definitions
let passes = ss.passes[13...13]
XCTAssertEqual(passes.compactMap(\.scaleX), [])
XCTAssertEqual(passes.compactMap(\.scaleY), [])
}

} catch {
Expand Down
144 changes: 87 additions & 57 deletions Source/FilterChain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ final public class FilterChain: ScreenshotSource {
private var lastPassIndex: Int = 0
private var historyCount: Int = 0

private var texture: MTLTexture? // final render texture
private var sourceTextures = [Texture](repeating: .init(), count: Constants.maxFrameHistory + 1)
// The OriginalHistory(N) semantic
private var historyTextures = [Texture](repeating: .init(), count: Constants.maxFrameHistory + 1)

private struct OutputFrame {
var viewport: MTLViewport
Expand Down Expand Up @@ -322,42 +322,22 @@ final public class FilterChain: ScreenshotSource {

public var sourceTexture: MTLTexture? {
didSet {
pixelBuffer = nil
texture = nil
pixelBuffer = nil
_pixelBufferTexture = nil
}
}

private func updateHistory() {
if hasShader {
if historyCount > 0 {
if historyNeedsInit {
return initHistory()
} else {
let tmp = sourceTextures[historyCount]
for k in (1...historyCount).reversed() {
sourceTextures[k] = sourceTextures[k - 1]
}
sourceTextures[0] = tmp
}
}
}
guard historyCount > 0 else { return }

if let sourceTexture = sourceTexture, historyCount == 0 {
initTexture(&sourceTextures[0], withTexture: sourceTexture)
return
}

// either no history, or we moved a texture of a different size in the front slot
if sourceTextures[0].size.x != Float(sourceRect.width) || sourceTextures[0].size.y != Float(sourceRect.height) {
let td = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm,
width: Int(sourceRect.width),
height: Int(sourceRect.height),
mipmapped: false)
td.storageMode = .private
td.usage = [.shaderRead, .shaderWrite]
initTexture(&sourceTextures[0], withDescriptor: td)
// Ensure the history texture is cleared before first use
_clearTextures.append(sourceTextures[0].view!)
if historyNeedsInit {
initHistory()
} else {
let tmp = historyTextures[historyCount]
for k in (1...historyCount).reversed() {
historyTextures[k] = historyTextures[k - 1]
}
historyTextures[0] = tmp
}
}

Expand Down Expand Up @@ -502,31 +482,67 @@ final public class FilterChain: ScreenshotSource {
}
}

private func preparePixelBuffer(_ pixelBuffer: PixelBuffer, commandBuffer: MTLCommandBuffer) {
private func fetchNextHistoryTexture() -> MTLTexture {
precondition(historyCount > 0, "Current shader does not require history")

// either no history, or we moved a texture of a different size in the front slot
if historyTextures[0].size.x != Float(sourceRect.width) || historyTextures[0].size.y != Float(sourceRect.height) {
let td = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm,
width: Int(sourceRect.width),
height: Int(sourceRect.height),
mipmapped: false)
td.storageMode = .private
td.usage = [.shaderRead, .shaderWrite]
initTexture(&historyTextures[0], withDescriptor: td)
}

return historyTextures[0].view!
}

private func prepareNextFrameWithCommandBuffer(_ commandBuffer: MTLCommandBuffer) {
frameCount += 1
resizeRenderTargets()
updateHistory()
clearTexturesWithCommandBuffer(commandBuffer)

texture = sourceTextures[0].view
guard let texture = texture else { return }
/// Target texture used by pixel buffer when the current shader does not require history
private var _pixelBufferTexture: MTLTexture?

private var pixelBufferTexture: MTLTexture {
if let _pixelBufferTexture = _pixelBufferTexture,
_pixelBufferTexture.width == Int(sourceRect.width) &&
_pixelBufferTexture.height == Int(sourceRect.height) {
return _pixelBufferTexture
}

if let pixelBuffer = pixelBuffer {
pixelBuffer.prepare(withCommandBuffer: commandBuffer, texture: texture)
return
let td = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm,
width: Int(sourceRect.width),
height: Int(sourceRect.height),
mipmapped: false)
td.storageMode = .private
td.usage = [.shaderRead, .shaderWrite]
if let tex = device.makeTexture(descriptor: td) {
_pixelBufferTexture = tex
return tex
}

fatalError("Unable to create pixelBufferTexture")
}

private func preparePixelBuffer(_ pixelBuffer: PixelBuffer, commandBuffer: MTLCommandBuffer) {
var texture: MTLTexture
if historyCount == 0 {
// When historyCount is 0, sourceTextures[0].view == sourceTexture
return
texture = pixelBufferTexture
if historyTextures[0].view == nil {
initTexture(&historyTextures[0], withTexture: texture)
}
} else {
texture = fetchNextHistoryTexture()
}

if let sourceTexture = sourceTexture {
pixelBuffer.prepare(withCommandBuffer: commandBuffer, texture: texture)
}

private func prepareSourceTexture(_ sourceTexture: MTLTexture, commandBuffer: MTLCommandBuffer) {
if historyCount == 0 {
// save a copy by setting the sourceTexture to Original / OriginalHistory0
initTexture(&historyTextures[0], withTexture: sourceTexture)
} else {
let texture = fetchNextHistoryTexture()

let orig = MTLOrigin(x: Int(sourceRect.origin.x), y: Int(sourceRect.origin.y), z: 0)
let size = MTLSize(width: Int(sourceRect.width), height: Int(sourceRect.height), depth: 1)
let zero = MTLOrigin()
Expand All @@ -539,6 +555,20 @@ final public class FilterChain: ScreenshotSource {
}
}

private func prepareNextFrameWithCommandBuffer(_ commandBuffer: MTLCommandBuffer) {
frameCount += 1

resizeRenderTargets()
updateHistory()
clearTexturesWithCommandBuffer(commandBuffer)

if let pixelBuffer = pixelBuffer {
preparePixelBuffer(pixelBuffer, commandBuffer: commandBuffer)
} else if let sourceTexture = sourceTexture {
prepareSourceTexture(sourceTexture, commandBuffer: commandBuffer)
}
}

private func initTexture(_ t: inout Texture, withDescriptor td: MTLTextureDescriptor) {
t.view = device.makeTexture(descriptor: td)
t.size = .init(width: td.width, height: td.height)
Expand All @@ -558,8 +588,8 @@ final public class FilterChain: ScreenshotSource {
td.usage = [.shaderRead, .shaderWrite]

for i in 0...historyCount {
initTexture(&sourceTextures[i], withDescriptor: td)
_clearTextures.append(sourceTextures[i].view!)
initTexture(&historyTextures[i], withDescriptor: td)
_clearTextures.append(historyTextures[i].view!)
}
historyNeedsInit = false
}
Expand All @@ -575,7 +605,7 @@ final public class FilterChain: ScreenshotSource {

public func renderSource(withCommandBuffer commandBuffer: MTLCommandBuffer) -> MTLTexture {
prepareNextFrameWithCommandBuffer(commandBuffer)
return texture!
return historyTextures[0].view!
}

public func render(withCommandBuffer commandBuffer: MTLCommandBuffer,
Expand Down Expand Up @@ -684,7 +714,7 @@ final public class FilterChain: ScreenshotSource {
}

if !hasShader || passCount == 0 {
guard let texture = texture else { return }
guard let texture = historyTextures[0].view else { return }
return renderTexture(texture, renderCommandEncoder: rce)
}

Expand Down Expand Up @@ -762,7 +792,7 @@ final public class FilterChain: ScreenshotSource {
private func freeShaderResources() {
pass = .init(repeating: .init(), count: Constants.maxShaderPasses)
luts = .init(repeating: .init(), count: Constants.maxTextures)
sourceTextures = .init(repeating: .init(), count: Constants.maxFrameHistory + 1)
historyTextures = .init(repeating: .init(), count: Constants.maxFrameHistory + 1)

parameters = .init(repeating: 0, count: Constants.maxParameters)
parametersMap = [:]
Expand Down Expand Up @@ -797,7 +827,7 @@ final public class FilterChain: ScreenshotSource {
for passNumber in 0..<passCount {
let sem = ShaderPassSemantics()

withUnsafePointer(to: &sourceTextures[0]) {
withUnsafePointer(to: &historyTextures[0]) {
let p = UnsafeRawPointer($0)
sem.addTexture(p.advanced(by: texViewOffset),
size: p.advanced(by: texSizeOffset),
Expand All @@ -808,7 +838,7 @@ final public class FilterChain: ScreenshotSource {
semantic: .originalHistory)

if passNumber == 0 {
// The source texture for first pass is the original input
// The source texture for first pass is the original input texture
sem.addTexture(p.advanced(by: texViewOffset),
size: p.advanced(by: texSizeOffset),
semantic: .source)
Expand Down

0 comments on commit 728c7d2

Please sign in to comment.