mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-07-06 21:05:20 +02:00
Merge pull request #46 from dscyrescotti/feature/photo-background-render
Exclude photo from erasing strokes
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; };
|
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; };
|
||||||
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5050062BF65CED00B4D86E /* PenDropDelegate.swift */; };
|
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5050062BF65CED00B4D86E /* PenDropDelegate.swift */; };
|
||||||
EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC50500C2BF6674400B4D86E /* OnDragViewModifier.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 */; };
|
EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */; };
|
||||||
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; };
|
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; };
|
||||||
EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = "<group>"; };
|
||||||
@@ -624,6 +626,7 @@
|
|||||||
ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */,
|
ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */,
|
||||||
ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */,
|
ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */,
|
||||||
ECD12A902C1B04EA00B96E12 /* PhotoRenderPass.swift */,
|
ECD12A902C1B04EA00B96E12 /* PhotoRenderPass.swift */,
|
||||||
|
EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */,
|
||||||
);
|
);
|
||||||
path = RenderPasses;
|
path = RenderPasses;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -843,6 +846,7 @@
|
|||||||
files = (
|
files = (
|
||||||
ECA738B02BE60D0B00A4542E /* CanvasViewController.swift in Sources */,
|
ECA738B02BE60D0B00A4542E /* CanvasViewController.swift in Sources */,
|
||||||
ECD12A912C1B04EA00B96E12 /* PhotoRenderPass.swift in Sources */,
|
ECD12A912C1B04EA00B96E12 /* PhotoRenderPass.swift in Sources */,
|
||||||
|
EC5D40812C21CE270067F090 /* PhotoBackgroundRenderPass.swift in Sources */,
|
||||||
ECA738E42BE6110800A4542E /* Drawable.swift in Sources */,
|
ECA738E42BE6110800A4542E /* Drawable.swift in Sources */,
|
||||||
ECA738AD2BE60CC600A4542E /* DrawingView.swift in Sources */,
|
ECA738AD2BE60CC600A4542E /* DrawingView.swift in Sources */,
|
||||||
EC1B783D2BFA0AC9005A34E2 /* Toolbar.swift in Sources */,
|
EC1B783D2BFA0AC9005A34E2 /* Toolbar.swift in Sources */,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ struct PipelineStates {
|
|||||||
let library = renderer.library
|
let library = renderer.library
|
||||||
let pipelineDescriptor = MTLRenderPipelineDescriptor()
|
let pipelineDescriptor = MTLRenderPipelineDescriptor()
|
||||||
pipelineDescriptor.vertexFunction = library.makeFunction(name: "vertex_stroke")
|
pipelineDescriptor.vertexFunction = library.makeFunction(name: "vertex_stroke")
|
||||||
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "fragment_stroke")
|
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "fragment_stroke_eraser")
|
||||||
pipelineDescriptor.colorAttachments[0].pixelFormat = pixelFormat ?? renderer.pixelFormat
|
pipelineDescriptor.colorAttachments[0].pixelFormat = pixelFormat ?? renderer.pixelFormat
|
||||||
pipelineDescriptor.label = "Eraser Pipeline State"
|
pipelineDescriptor.label = "Eraser Pipeline State"
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ final class Renderer {
|
|||||||
lazy var viewPortRenderPass: ViewPortRenderPass = {
|
lazy var viewPortRenderPass: ViewPortRenderPass = {
|
||||||
ViewPortRenderPass(renderer: self)
|
ViewPortRenderPass(renderer: self)
|
||||||
}()
|
}()
|
||||||
|
lazy var photoBackgroundRenderPass: PhotoBackgroundRenderPass = {
|
||||||
|
PhotoBackgroundRenderPass(renderer: self)
|
||||||
|
}()
|
||||||
|
|
||||||
init(canvasView: MTKView) {
|
init(canvasView: MTKView) {
|
||||||
guard let device = MTLCreateSystemDefaultDevice() else {
|
guard let device = MTLCreateSystemDefaultDevice() else {
|
||||||
@@ -59,6 +62,7 @@ final class Renderer {
|
|||||||
|
|
||||||
func resize(on view: MTKView, to size: CGSize) {
|
func resize(on view: MTKView, to size: CGSize) {
|
||||||
if !updatesViewPort {
|
if !updatesViewPort {
|
||||||
|
photoBackgroundRenderPass.resize(on: view, to: size, with: self)
|
||||||
strokeRenderPass.resize(on: view, to: size, with: self)
|
strokeRenderPass.resize(on: view, to: size, with: self)
|
||||||
graphicRenderPass.resize(on: view, to: size, with: self)
|
graphicRenderPass.resize(on: view, to: size, with: self)
|
||||||
cacheRenderPass.resize(on: view, to: size, with: self)
|
cacheRenderPass.resize(on: view, to: size, with: self)
|
||||||
@@ -69,9 +73,11 @@ final class Renderer {
|
|||||||
|
|
||||||
func draw(in view: MTKView, on canvas: Canvas) {
|
func draw(in view: MTKView, on canvas: Canvas) {
|
||||||
if !updatesViewPort {
|
if !updatesViewPort {
|
||||||
|
strokeRenderPass.eraserRenderPass = eraserRenderPass
|
||||||
graphicRenderPass.photoRenderPass = photoRenderPass
|
graphicRenderPass.photoRenderPass = photoRenderPass
|
||||||
graphicRenderPass.strokeRenderPass = strokeRenderPass
|
graphicRenderPass.strokeRenderPass = strokeRenderPass
|
||||||
graphicRenderPass.eraserRenderPass = eraserRenderPass
|
graphicRenderPass.eraserRenderPass = eraserRenderPass
|
||||||
|
graphicRenderPass.photoBackgroundRenderPass = photoBackgroundRenderPass
|
||||||
graphicRenderPass.draw(on: canvas, with: self)
|
graphicRenderPass.draw(on: canvas, with: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +90,7 @@ final class Renderer {
|
|||||||
cacheRenderPass.draw(on: canvas, with: self)
|
cacheRenderPass.draw(on: canvas, with: self)
|
||||||
|
|
||||||
viewPortRenderPass.descriptor = view.currentRenderPassDescriptor
|
viewPortRenderPass.descriptor = view.currentRenderPassDescriptor
|
||||||
|
viewPortRenderPass.photoBackgroundTexture = photoBackgroundRenderPass.photoBackgroundTexture
|
||||||
viewPortRenderPass.cacheTexture = cacheRenderPass.cacheTexture
|
viewPortRenderPass.cacheTexture = cacheRenderPass.cacheTexture
|
||||||
viewPortRenderPass.draw(on: canvas, with: self)
|
viewPortRenderPass.draw(on: canvas, with: self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,4 +116,27 @@ class Textures {
|
|||||||
texture.label = "Stroke Texture"
|
texture.label = "Stroke Texture"
|
||||||
return 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,13 +83,17 @@ extension Stroke {
|
|||||||
extension Stroke {
|
extension Stroke {
|
||||||
func prepare(device: MTLDevice) {
|
func prepare(device: MTLDevice) {
|
||||||
guard texture == nil else { return }
|
guard texture == nil else { return }
|
||||||
texture = penStyle.loadTexture(on: device)
|
if penStyle.textureName != nil {
|
||||||
|
texture = penStyle.loadTexture(on: device)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) {
|
func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) {
|
||||||
guard !isEmpty, let indexBuffer else { return }
|
guard !isEmpty, let indexBuffer else { return }
|
||||||
prepare(device: device)
|
prepare(device: device)
|
||||||
renderEncoder.setFragmentTexture(texture, index: 0)
|
if penStyle.textureName != nil {
|
||||||
|
renderEncoder.setFragmentTexture(texture, index: 0)
|
||||||
|
}
|
||||||
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
||||||
renderEncoder.drawIndexedPrimitives(
|
renderEncoder.drawIndexedPrimitives(
|
||||||
type: .triangle,
|
type: .triangle,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class GraphicRenderPass: RenderPass {
|
|||||||
weak var photoRenderPass: PhotoRenderPass?
|
weak var photoRenderPass: PhotoRenderPass?
|
||||||
weak var strokeRenderPass: StrokeRenderPass?
|
weak var strokeRenderPass: StrokeRenderPass?
|
||||||
weak var eraserRenderPass: EraserRenderPass?
|
weak var eraserRenderPass: EraserRenderPass?
|
||||||
|
weak var photoBackgroundRenderPass: PhotoBackgroundRenderPass?
|
||||||
|
|
||||||
var clearsTexture: Bool = true
|
var clearsTexture: Bool = true
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ class GraphicRenderPass: RenderPass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer) {
|
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 descriptor else { return }
|
||||||
|
|
||||||
guard let graphicPipelineState else { return }
|
guard let graphicPipelineState else { return }
|
||||||
@@ -55,7 +56,6 @@ class GraphicRenderPass: RenderPass {
|
|||||||
let stroke = _stroke.value
|
let stroke = _stroke.value
|
||||||
guard stroke.isVisible(in: canvas.bounds) else { continue }
|
guard stroke.isVisible(in: canvas.bounds) else { continue }
|
||||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||||
clearsTexture = false
|
|
||||||
switch stroke.style {
|
switch stroke.style {
|
||||||
case .eraser:
|
case .eraser:
|
||||||
eraserRenderPass.stroke = stroke
|
eraserRenderPass.stroke = stroke
|
||||||
@@ -67,20 +67,19 @@ class GraphicRenderPass: RenderPass {
|
|||||||
strokeRenderPass.graphicDescriptor = descriptor
|
strokeRenderPass.graphicDescriptor = descriptor
|
||||||
strokeRenderPass.graphicPipelineState = graphicPipelineState
|
strokeRenderPass.graphicPipelineState = graphicPipelineState
|
||||||
strokeRenderPass.draw(on: canvas, with: renderer)
|
strokeRenderPass.draw(on: canvas, with: renderer)
|
||||||
|
|
||||||
if let stroke = stroke as? PenStroke, !stroke.isEmptyErasedQuads {
|
|
||||||
descriptor.colorAttachments[0].loadAction = .load
|
|
||||||
eraserRenderPass.stroke = stroke
|
|
||||||
eraserRenderPass.descriptor = descriptor
|
|
||||||
eraserRenderPass.draw(on: canvas, with: renderer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
clearsTexture = false
|
||||||
case .photo(let photo):
|
case .photo(let photo):
|
||||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||||
clearsTexture = false
|
|
||||||
photoRenderPass.photo = photo
|
photoRenderPass.photo = photo
|
||||||
photoRenderPass.descriptor = descriptor
|
photoRenderPass.descriptor = descriptor
|
||||||
photoRenderPass.draw(on: canvas, with: renderer)
|
photoRenderPass.draw(on: canvas, with: renderer)
|
||||||
|
|
||||||
|
photoBackgroundRenderPass.photo = photo
|
||||||
|
photoBackgroundRenderPass.clearsTexture = clearsTexture
|
||||||
|
photoBackgroundRenderPass.draw(on: canvas, with: renderer)
|
||||||
|
|
||||||
|
clearsTexture = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderer.redrawsGraphicRender = false
|
renderer.redrawsGraphicRender = false
|
||||||
@@ -88,7 +87,6 @@ class GraphicRenderPass: RenderPass {
|
|||||||
|
|
||||||
if let element = graphicContext.previousElement {
|
if let element = graphicContext.previousElement {
|
||||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||||
clearsTexture = false
|
|
||||||
switch element {
|
switch element {
|
||||||
case .stroke(let anyStroke):
|
case .stroke(let anyStroke):
|
||||||
let stroke = anyStroke.value
|
let stroke = anyStroke.value
|
||||||
@@ -103,19 +101,17 @@ class GraphicRenderPass: RenderPass {
|
|||||||
strokeRenderPass.graphicDescriptor = descriptor
|
strokeRenderPass.graphicDescriptor = descriptor
|
||||||
strokeRenderPass.graphicPipelineState = graphicPipelineState
|
strokeRenderPass.graphicPipelineState = graphicPipelineState
|
||||||
strokeRenderPass.draw(on: canvas, with: renderer)
|
strokeRenderPass.draw(on: canvas, with: renderer)
|
||||||
|
|
||||||
if let stroke = stroke as? PenStroke, !stroke.isEmptyErasedQuads {
|
|
||||||
descriptor.colorAttachments[0].loadAction = .load
|
|
||||||
eraserRenderPass.stroke = stroke
|
|
||||||
eraserRenderPass.descriptor = descriptor
|
|
||||||
eraserRenderPass.draw(on: canvas, with: renderer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case .photo(let photo):
|
case .photo(let photo):
|
||||||
photoRenderPass.photo = photo
|
photoRenderPass.photo = photo
|
||||||
photoRenderPass.descriptor = descriptor
|
photoRenderPass.descriptor = descriptor
|
||||||
photoRenderPass.draw(on: canvas, with: renderer)
|
photoRenderPass.draw(on: canvas, with: renderer)
|
||||||
|
|
||||||
|
photoBackgroundRenderPass.photo = photo
|
||||||
|
photoBackgroundRenderPass.clearsTexture = clearsTexture
|
||||||
|
photoBackgroundRenderPass.draw(on: canvas, with: renderer)
|
||||||
}
|
}
|
||||||
|
clearsTexture = false
|
||||||
graphicContext.previousElement = nil
|
graphicContext.previousElement = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
//
|
||||||
|
// 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()
|
||||||
|
commandBuffer.waitUntilCompleted()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,5 +41,6 @@ class PhotoRenderPass: RenderPass {
|
|||||||
|
|
||||||
renderEncoder.endEncoding()
|
renderEncoder.endEncoding()
|
||||||
commandBuffer.commit()
|
commandBuffer.commit()
|
||||||
|
commandBuffer.waitUntilCompleted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ class StrokeRenderPass: RenderPass {
|
|||||||
var stroke: (any Stroke)?
|
var stroke: (any Stroke)?
|
||||||
var strokeTexture: MTLTexture?
|
var strokeTexture: MTLTexture?
|
||||||
|
|
||||||
|
weak var eraserRenderPass: EraserRenderPass?
|
||||||
|
|
||||||
init(renderer: Renderer) {
|
init(renderer: Renderer) {
|
||||||
descriptor = MTLRenderPassDescriptor()
|
descriptor = MTLRenderPassDescriptor()
|
||||||
strokePipelineState = PipelineStates.createStrokePipelineState(from: renderer)
|
strokePipelineState = PipelineStates.createStrokePipelineState(from: renderer)
|
||||||
@@ -57,6 +59,13 @@ class StrokeRenderPass: RenderPass {
|
|||||||
renderEncoder.endEncoding()
|
renderEncoder.endEncoding()
|
||||||
commandBuffer.commit()
|
commandBuffer.commit()
|
||||||
|
|
||||||
|
if let eraserRenderPass, let stroke = stroke as? PenStroke, !stroke.isEmptyErasedQuads {
|
||||||
|
descriptor.colorAttachments[0].loadAction = .load
|
||||||
|
eraserRenderPass.stroke = stroke
|
||||||
|
eraserRenderPass.descriptor = descriptor
|
||||||
|
eraserRenderPass.draw(on: canvas, with: renderer)
|
||||||
|
}
|
||||||
|
|
||||||
drawStrokeTexture(on: canvas, with: renderer)
|
drawStrokeTexture(on: canvas, with: renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class ViewPortRenderPass: RenderPass {
|
|||||||
var viewPortUpdatePipelineState: MTLRenderPipelineState?
|
var viewPortUpdatePipelineState: MTLRenderPipelineState?
|
||||||
|
|
||||||
weak var cacheTexture: MTLTexture?
|
weak var cacheTexture: MTLTexture?
|
||||||
|
weak var photoBackgroundTexture: MTLTexture?
|
||||||
|
|
||||||
weak var view: MTKView?
|
weak var view: MTKView?
|
||||||
|
|
||||||
@@ -51,6 +52,10 @@ class ViewPortRenderPass: RenderPass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderEncoder.setRenderPipelineState(viewPortUpdatePipelineState)
|
renderEncoder.setRenderPipelineState(viewPortUpdatePipelineState)
|
||||||
|
|
||||||
|
renderEncoder.setFragmentTexture(photoBackgroundTexture, index: 0)
|
||||||
|
canvas.renderViewPortUpdate(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
|
|
||||||
renderEncoder.setFragmentTexture(cacheTexture, index: 0)
|
renderEncoder.setFragmentTexture(cacheTexture, index: 0)
|
||||||
canvas.renderViewPortUpdate(device: renderer.device, renderEncoder: renderEncoder)
|
canvas.renderViewPortUpdate(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
} else {
|
} else {
|
||||||
@@ -59,6 +64,10 @@ class ViewPortRenderPass: RenderPass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderEncoder.setRenderPipelineState(viewPortPipelineState)
|
renderEncoder.setRenderPipelineState(viewPortPipelineState)
|
||||||
|
|
||||||
|
renderEncoder.setFragmentTexture(photoBackgroundTexture, index: 0)
|
||||||
|
canvas.renderViewPort(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
|
|
||||||
renderEncoder.setFragmentTexture(cacheTexture, index: 0)
|
renderEncoder.setFragmentTexture(cacheTexture, index: 0)
|
||||||
canvas.renderViewPort(device: renderer.device, renderEncoder: renderEncoder)
|
canvas.renderViewPort(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,3 +53,21 @@ fragment float4 fragment_stroke(
|
|||||||
float4 color = float4(texture.sample(textureSampler, out.textCoord));
|
float4 color = float4(texture.sample(textureSampler, out.textCoord));
|
||||||
return float4(1, 1, 1, color.a);
|
return float4(1, 1, 1, color.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fragment float4 fragment_stroke_eraser(
|
||||||
|
VertexOut out [[stage_in]],
|
||||||
|
texture2d<float> texture [[texture(0)]]
|
||||||
|
) {
|
||||||
|
float2 circleCenter = float2(0.5, 0.5);
|
||||||
|
float radius = 0.4;
|
||||||
|
float4 circleColor = float4(1.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
float2 fragCoord = out.textCoord;
|
||||||
|
float distance = length(fragCoord - circleCenter);
|
||||||
|
|
||||||
|
if (distance < radius) {
|
||||||
|
return circleColor;
|
||||||
|
} else {
|
||||||
|
return float4(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
protocol PenStyle {
|
protocol PenStyle {
|
||||||
var icon: (base: String, tip: String?) { get }
|
var icon: (base: String, tip: String?) { get }
|
||||||
var textureName: String { get }
|
var textureName: String? { get }
|
||||||
var thickness: (min: CGFloat, max: CGFloat) { get }
|
var thickness: (min: CGFloat, max: CGFloat) { get }
|
||||||
var thicknessSteps: [CGFloat] { get }
|
var thicknessSteps: [CGFloat] { get }
|
||||||
var color: [CGFloat] { get }
|
var color: [CGFloat] { get }
|
||||||
@@ -22,6 +22,7 @@ protocol PenStyle {
|
|||||||
extension PenStyle {
|
extension PenStyle {
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func loadTexture(on device: MTLDevice) -> MTLTexture? {
|
func loadTexture(on device: MTLDevice) -> MTLTexture? {
|
||||||
Textures.createPenTexture(with: textureName, on: device)
|
guard let textureName else { return nil }
|
||||||
|
return Textures.createPenTexture(with: textureName, on: device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import Foundation
|
|||||||
struct EraserPenStyle: PenStyle {
|
struct EraserPenStyle: PenStyle {
|
||||||
var icon: (base: String, tip: String?) = ("eraser", nil)
|
var icon: (base: String, tip: String?) = ("eraser", nil)
|
||||||
|
|
||||||
var textureName: String = "point-texture"
|
var textureName: String? = nil
|
||||||
|
|
||||||
var thickness: (min: CGFloat, max: CGFloat) = (0.5, 30)
|
var thickness: (min: CGFloat, max: CGFloat) = (0.5, 30)
|
||||||
|
|
||||||
var thicknessSteps: [CGFloat] = [0.5, 1, 2, 5, 7.5, 10, 12.5, 15, 17.5, 20, 25, 30]
|
var thicknessSteps: [CGFloat] = [0.5, 1, 2, 5, 7.5, 10, 12.5, 15, 17.5, 20, 25, 30]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Foundation
|
|||||||
struct MarkerPenStyle: PenStyle {
|
struct MarkerPenStyle: PenStyle {
|
||||||
var icon: (base: String, tip: String?) = ("marker-base", "marker-tip")
|
var icon: (base: String, tip: String?) = ("marker-base", "marker-tip")
|
||||||
|
|
||||||
var textureName: String = "point-texture"
|
var textureName: String? = "point-texture"
|
||||||
|
|
||||||
var thickness: (min: CGFloat, max: CGFloat) = (0.5, 30)
|
var thickness: (min: CGFloat, max: CGFloat) = (0.5, 30)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user