feat: add photo background render pass

This commit is contained in:
dscyrescotti
2024-06-18 22:21:12 +07:00
parent efaf63b882
commit e5f7bec775
6 changed files with 112 additions and 4 deletions

View File

@@ -25,6 +25,7 @@
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; };
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5050062BF65CED00B4D86E /* PenDropDelegate.swift */; };
EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */; };
EC5D40812C21CE270067F090 /* PhotoBackgroundRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */; };
EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */; };
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; };
EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; };
@@ -127,6 +128,7 @@
EC5050062BF65CED00B4D86E /* PenDropDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDropDelegate.swift; sourceTree = "<group>"; };
EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnDragViewModifier.swift; sourceTree = "<group>"; };
EC50500E2BF670EA00B4D86E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoBackgroundRenderPass.swift; sourceTree = "<group>"; };
EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovingAverage.swift; sourceTree = "<group>"; };
EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; };
EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = "<group>"; };
@@ -624,6 +626,7 @@
ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */,
ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */,
ECD12A902C1B04EA00B96E12 /* PhotoRenderPass.swift */,
EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */,
);
path = RenderPasses;
sourceTree = "<group>";
@@ -843,6 +846,7 @@
files = (
ECA738B02BE60D0B00A4542E /* CanvasViewController.swift in Sources */,
ECD12A912C1B04EA00B96E12 /* PhotoRenderPass.swift in Sources */,
EC5D40812C21CE270067F090 /* PhotoBackgroundRenderPass.swift in Sources */,
ECA738E42BE6110800A4542E /* Drawable.swift in Sources */,
ECA738AD2BE60CC600A4542E /* DrawingView.swift in Sources */,
EC1B783D2BFA0AC9005A34E2 /* Toolbar.swift in Sources */,

View File

@@ -37,6 +37,9 @@ final class Renderer {
lazy var viewPortRenderPass: ViewPortRenderPass = {
ViewPortRenderPass(renderer: self)
}()
lazy var photoBackgroundRenderPass: PhotoBackgroundRenderPass = {
PhotoBackgroundRenderPass(renderer: self)
}()
init(canvasView: MTKView) {
guard let device = MTLCreateSystemDefaultDevice() else {
@@ -59,6 +62,7 @@ final class Renderer {
func resize(on view: MTKView, to size: CGSize) {
if !updatesViewPort {
photoBackgroundRenderPass.resize(on: view, to: size, with: self)
strokeRenderPass.resize(on: view, to: size, with: self)
graphicRenderPass.resize(on: view, to: size, with: self)
cacheRenderPass.resize(on: view, to: size, with: self)
@@ -73,6 +77,7 @@ final class Renderer {
graphicRenderPass.photoRenderPass = photoRenderPass
graphicRenderPass.strokeRenderPass = strokeRenderPass
graphicRenderPass.eraserRenderPass = eraserRenderPass
graphicRenderPass.photoBackgroundRenderPass = photoBackgroundRenderPass
graphicRenderPass.draw(on: canvas, with: self)
}
@@ -85,6 +90,7 @@ final class Renderer {
cacheRenderPass.draw(on: canvas, with: self)
viewPortRenderPass.descriptor = view.currentRenderPassDescriptor
viewPortRenderPass.photoBackgroundTexture = photoBackgroundRenderPass.photoBackgroundTexture
viewPortRenderPass.cacheTexture = cacheRenderPass.cacheTexture
viewPortRenderPass.draw(on: canvas, with: self)
}

View File

@@ -116,4 +116,27 @@ class Textures {
texture.label = "Stroke Texture"
return texture
}
static func createPhotoBackgroundTexture(
from renderer: Renderer,
size: CGSize,
pixelFormat: MTLPixelFormat? = nil
) -> MTLTexture? {
let width = Int(size.width)
let height = Int(size.height)
guard width > 0, height > 0 else { return nil }
let descriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: pixelFormat ?? renderer.pixelFormat,
width: width,
height: height,
mipmapped: false
)
descriptor.storageMode = .shared
descriptor.usage = [.shaderRead, .renderTarget, .shaderWrite]
guard let texture = renderer.device.makeTexture(descriptor: descriptor) else {
return nil
}
texture.label = "Photo Background Texture"
return texture
}
}

View File

@@ -18,6 +18,7 @@ class GraphicRenderPass: RenderPass {
weak var photoRenderPass: PhotoRenderPass?
weak var strokeRenderPass: StrokeRenderPass?
weak var eraserRenderPass: EraserRenderPass?
weak var photoBackgroundRenderPass: PhotoBackgroundRenderPass?
var clearsTexture: Bool = true
@@ -33,7 +34,7 @@ class GraphicRenderPass: RenderPass {
}
func draw(on canvas: Canvas, with renderer: Renderer) {
guard let strokeRenderPass, let eraserRenderPass, let photoRenderPass else { return }
guard let strokeRenderPass, let eraserRenderPass, let photoRenderPass, let photoBackgroundRenderPass else { return }
guard let descriptor else { return }
guard let graphicPipelineState else { return }
@@ -55,7 +56,6 @@ class GraphicRenderPass: RenderPass {
let stroke = _stroke.value
guard stroke.isVisible(in: canvas.bounds) else { continue }
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
clearsTexture = false
switch stroke.style {
case .eraser:
eraserRenderPass.stroke = stroke
@@ -68,12 +68,18 @@ class GraphicRenderPass: RenderPass {
strokeRenderPass.graphicPipelineState = graphicPipelineState
strokeRenderPass.draw(on: canvas, with: renderer)
}
clearsTexture = false
case .photo(let photo):
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
clearsTexture = false
photoRenderPass.photo = photo
photoRenderPass.descriptor = descriptor
photoRenderPass.draw(on: canvas, with: renderer)
photoBackgroundRenderPass.photo = photo
photoBackgroundRenderPass.clearsTexture = clearsTexture
photoBackgroundRenderPass.draw(on: canvas, with: renderer)
clearsTexture = false
}
}
renderer.redrawsGraphicRender = false
@@ -81,7 +87,6 @@ class GraphicRenderPass: RenderPass {
if let element = graphicContext.previousElement {
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
clearsTexture = false
switch element {
case .stroke(let anyStroke):
let stroke = anyStroke.value
@@ -101,7 +106,12 @@ class GraphicRenderPass: RenderPass {
photoRenderPass.photo = photo
photoRenderPass.descriptor = descriptor
photoRenderPass.draw(on: canvas, with: renderer)
photoBackgroundRenderPass.photo = photo
photoBackgroundRenderPass.clearsTexture = clearsTexture
photoBackgroundRenderPass.draw(on: canvas, with: renderer)
}
clearsTexture = false
graphicContext.previousElement = nil
}

View File

@@ -0,0 +1,56 @@
//
// PhotoBackgroundRenderPass.swift
// Memola
//
// Created by Dscyre Scotti on 6/18/24.
//
import MetalKit
import Foundation
class PhotoBackgroundRenderPass: RenderPass {
var label: String = "Photo Background Render Pass"
var descriptor: MTLRenderPassDescriptor?
var photoBackgroundPipelineState: MTLRenderPipelineState?
var photoBackgroundTexture: MTLTexture?
var photo: Photo?
var clearsTexture: Bool = true
init(renderer: Renderer) {
descriptor = MTLRenderPassDescriptor()
photoBackgroundPipelineState = PipelineStates.createPhotoPipelineState(from: renderer)
}
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer) {
photoBackgroundTexture = Textures.createPhotoBackgroundTexture(from: renderer, size: size, pixelFormat: renderer.pixelFormat)
}
func draw(on canvas: Canvas, with renderer: Renderer) {
guard let descriptor else { return }
descriptor.colorAttachments[0].texture = photoBackgroundTexture
descriptor.colorAttachments[0].storeAction = .store
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 1, green: 1, blue: 1, alpha: 0)
guard let commandBuffer = renderer.commandQueue.makeCommandBuffer() else { return }
commandBuffer.label = "Photo Background Command Buffer"
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }
renderEncoder.label = label
guard let photoBackgroundPipelineState else { return }
renderEncoder.setRenderPipelineState(photoBackgroundPipelineState)
canvas.setUniformsBuffer(device: renderer.device, renderEncoder: renderEncoder)
photo?.draw(device: renderer.device, renderEncoder: renderEncoder)
renderEncoder.endEncoding()
commandBuffer.commit()
}
}

View File

@@ -17,6 +17,7 @@ class ViewPortRenderPass: RenderPass {
var viewPortUpdatePipelineState: MTLRenderPipelineState?
weak var cacheTexture: MTLTexture?
weak var photoBackgroundTexture: MTLTexture?
weak var view: MTKView?
@@ -51,6 +52,10 @@ class ViewPortRenderPass: RenderPass {
}
renderEncoder.setRenderPipelineState(viewPortUpdatePipelineState)
renderEncoder.setFragmentTexture(photoBackgroundTexture, index: 0)
canvas.renderViewPort(device: renderer.device, renderEncoder: renderEncoder)
renderEncoder.setFragmentTexture(cacheTexture, index: 0)
canvas.renderViewPortUpdate(device: renderer.device, renderEncoder: renderEncoder)
} else {
@@ -59,6 +64,10 @@ class ViewPortRenderPass: RenderPass {
}
renderEncoder.setRenderPipelineState(viewPortPipelineState)
renderEncoder.setFragmentTexture(photoBackgroundTexture, index: 0)
canvas.renderViewPort(device: renderer.device, renderEncoder: renderEncoder)
renderEncoder.setFragmentTexture(cacheTexture, index: 0)
canvas.renderViewPort(device: renderer.device, renderEncoder: renderEncoder)
}