mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-05-30 11:20:48 +02:00
chore: refactor render pipeline
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */ = {isa = PBXBuildFile; fileRef = EC3565592BF060D900A4E0BF /* Quad.metal */; };
|
EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */ = {isa = PBXBuildFile; fileRef = EC3565592BF060D900A4E0BF /* Quad.metal */; };
|
||||||
EC35655C2BF0712A00A4E0BF /* Float++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC35655B2BF0712A00A4E0BF /* Float++.swift */; };
|
EC35655C2BF0712A00A4E0BF /* Float++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC35655B2BF0712A00A4E0BF /* Float++.swift */; };
|
||||||
EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB112C1B2DD90008D976 /* ToolSelection.swift */; };
|
EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB112C1B2DD90008D976 /* ToolSelection.swift */; };
|
||||||
|
EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC42F7842C25267000E86E96 /* ElementGroup.swift */; };
|
||||||
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 */; };
|
||||||
@@ -124,6 +125,7 @@
|
|||||||
EC3565592BF060D900A4E0BF /* Quad.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Quad.metal; sourceTree = "<group>"; };
|
EC3565592BF060D900A4E0BF /* Quad.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Quad.metal; sourceTree = "<group>"; };
|
||||||
EC35655B2BF0712A00A4E0BF /* Float++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Float++.swift"; sourceTree = "<group>"; };
|
EC35655B2BF0712A00A4E0BF /* Float++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Float++.swift"; sourceTree = "<group>"; };
|
||||||
EC37FB112C1B2DD90008D976 /* ToolSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSelection.swift; sourceTree = "<group>"; };
|
EC37FB112C1B2DD90008D976 /* ToolSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSelection.swift; sourceTree = "<group>"; };
|
||||||
|
EC42F7842C25267000E86E96 /* ElementGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementGroup.swift; sourceTree = "<group>"; };
|
||||||
EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = "<group>"; };
|
EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = "<group>"; };
|
||||||
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>"; };
|
||||||
@@ -713,6 +715,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ECD12A892C19EFB000B96E12 /* Element.swift */,
|
ECD12A892C19EFB000B96E12 /* Element.swift */,
|
||||||
|
EC42F7842C25267000E86E96 /* ElementGroup.swift */,
|
||||||
);
|
);
|
||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -906,6 +909,7 @@
|
|||||||
ECA738F42BE612A000A4542E /* Array++.swift in Sources */,
|
ECA738F42BE612A000A4542E /* Array++.swift in Sources */,
|
||||||
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */,
|
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */,
|
||||||
ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */,
|
ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */,
|
||||||
|
EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */,
|
||||||
ECD12A8F2C1AEBA400B96E12 /* Photo.swift in Sources */,
|
ECD12A8F2C1AEBA400B96E12 /* Photo.swift in Sources */,
|
||||||
ECD12A932C1B062000B96E12 /* Photo.metal in Sources */,
|
ECD12A932C1B062000B96E12 /* Photo.metal in Sources */,
|
||||||
ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */,
|
ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */,
|
||||||
|
|||||||
@@ -12,5 +12,5 @@ protocol RenderPass {
|
|||||||
var label: String { get }
|
var label: String { get }
|
||||||
var descriptor: MTLRenderPassDescriptor? { get set }
|
var descriptor: MTLRenderPassDescriptor? { get set }
|
||||||
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer)
|
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer)
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer)
|
func draw(into commandBuffer: MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,13 +72,17 @@ final class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func draw(in view: MTKView, on canvas: Canvas) {
|
func draw(in view: MTKView, on canvas: Canvas) {
|
||||||
|
guard let commandBuffer = commandQueue.makeCommandBuffer() else {
|
||||||
|
NSLog("[Memola] - Unable to create command buffer")
|
||||||
|
return
|
||||||
|
}
|
||||||
if !updatesViewPort {
|
if !updatesViewPort {
|
||||||
strokeRenderPass.eraserRenderPass = eraserRenderPass
|
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.photoBackgroundRenderPass = photoBackgroundRenderPass
|
||||||
graphicRenderPass.draw(on: canvas, with: self)
|
graphicRenderPass.draw(into: commandBuffer, on: canvas, with: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheRenderPass.clearsTexture = graphicRenderPass.clearsTexture
|
cacheRenderPass.clearsTexture = graphicRenderPass.clearsTexture
|
||||||
@@ -87,11 +91,11 @@ final class Renderer {
|
|||||||
cacheRenderPass.eraserRenderPass = eraserRenderPass
|
cacheRenderPass.eraserRenderPass = eraserRenderPass
|
||||||
cacheRenderPass.graphicTexture = graphicRenderPass.graphicTexture
|
cacheRenderPass.graphicTexture = graphicRenderPass.graphicTexture
|
||||||
cacheRenderPass.graphicPipelineState = graphicRenderPass.graphicPipelineState
|
cacheRenderPass.graphicPipelineState = graphicRenderPass.graphicPipelineState
|
||||||
cacheRenderPass.draw(on: canvas, with: self)
|
cacheRenderPass.draw(into: commandBuffer, on: canvas, with: self)
|
||||||
|
|
||||||
viewPortRenderPass.descriptor = view.currentRenderPassDescriptor
|
viewPortRenderPass.descriptor = view.currentRenderPassDescriptor
|
||||||
viewPortRenderPass.photoBackgroundTexture = photoBackgroundRenderPass.photoBackgroundTexture
|
viewPortRenderPass.photoBackgroundTexture = photoBackgroundRenderPass.photoBackgroundTexture
|
||||||
viewPortRenderPass.cacheTexture = cacheRenderPass.cacheTexture
|
viewPortRenderPass.cacheTexture = cacheRenderPass.cacheTexture
|
||||||
viewPortRenderPass.draw(on: canvas, with: self)
|
viewPortRenderPass.draw(into: commandBuffer, on: canvas, with: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,18 @@ enum Element: Equatable, Comparable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var elementGroupType: ElementGroup.ElementGroupType {
|
||||||
|
switch self {
|
||||||
|
case .stroke(let anyStroke):
|
||||||
|
switch anyStroke.value.style {
|
||||||
|
case .marker: return .stroke
|
||||||
|
case .eraser: return .eraser
|
||||||
|
}
|
||||||
|
case .photo:
|
||||||
|
return .photo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static func < (lhs: Element, rhs: Element) -> Bool {
|
static func < (lhs: Element, rhs: Element) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case let (.stroke(leftStroke), .stroke(rightStroke)):
|
case let (.stroke(leftStroke), .stroke(rightStroke)):
|
||||||
@@ -53,4 +65,15 @@ enum Element: Equatable, Comparable {
|
|||||||
stroke.value.createdAt < photo.createdAt
|
stroke.value.createdAt < photo.createdAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func ^= (lhs: Element, rhs: Element) -> Bool {
|
||||||
|
switch (lhs, rhs) {
|
||||||
|
case let (.stroke(leftStroke), .stroke(rightStroke)):
|
||||||
|
leftStroke ^= rightStroke
|
||||||
|
case let (.photo(leftPhoto), .photo(rightPhoto)):
|
||||||
|
leftPhoto == rightPhoto
|
||||||
|
default:
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// ElementGroup.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/21/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class ElementGroup {
|
||||||
|
var elements: [Element] = []
|
||||||
|
var type: ElementGroupType
|
||||||
|
|
||||||
|
init(_ element: Element) {
|
||||||
|
elements = [element]
|
||||||
|
type = element.elementGroupType
|
||||||
|
}
|
||||||
|
|
||||||
|
var isEmpty: Bool { elements.isEmpty }
|
||||||
|
|
||||||
|
func add(_ element: Element) {
|
||||||
|
elements.append(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSameElement(_ element: Element) -> Bool {
|
||||||
|
guard let last = elements.last else { return false }
|
||||||
|
return element ^= last
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPenStyle() -> PenStyle? {
|
||||||
|
if let last = elements.last, case let .stroke(anyStroke) = last {
|
||||||
|
return anyStroke.value.penStyle
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPenColor() -> [CGFloat]? {
|
||||||
|
if let last = elements.last, case let .stroke(anyStroke) = last {
|
||||||
|
return anyStroke.value.color
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ElementGroup {
|
||||||
|
enum ElementGroupType {
|
||||||
|
case stroke
|
||||||
|
case eraser
|
||||||
|
case photo
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,10 @@ struct AnyStroke: Equatable, Comparable {
|
|||||||
lhs.value.id == rhs.value.id
|
lhs.value.id == rhs.value.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func ^= (lhs: AnyStroke, rhs: AnyStroke) -> Bool {
|
||||||
|
lhs.value.color == rhs.value.color && lhs.value.style == rhs.value.style
|
||||||
|
}
|
||||||
|
|
||||||
static func < (lhs: AnyStroke, rhs: AnyStroke) -> Bool {
|
static func < (lhs: AnyStroke, rhs: AnyStroke) -> Bool {
|
||||||
lhs.value.createdAt < rhs.value.createdAt
|
lhs.value.createdAt < rhs.value.createdAt
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol Stroke: AnyObject, Drawable, Hashable, Equatable, Comparable {
|
protocol Stroke: AnyObject, Drawable, Hashable, Equatable {
|
||||||
var id: UUID { get set }
|
var id: UUID { get set }
|
||||||
var bounds: [CGFloat] { get set }
|
var bounds: [CGFloat] { get set }
|
||||||
var color: [CGFloat] { get set }
|
var color: [CGFloat] { get set }
|
||||||
@@ -102,8 +102,6 @@ extension Stroke {
|
|||||||
indexBuffer: indexBuffer,
|
indexBuffer: indexBuffer,
|
||||||
indexBufferOffset: 0
|
indexBufferOffset: 0
|
||||||
)
|
)
|
||||||
self.vertexBuffer = nil
|
|
||||||
self.indexBuffer = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,5 @@ final class PenStroke: Stroke, @unchecked Sendable {
|
|||||||
indexBuffer: erasedIndexBuffer,
|
indexBuffer: erasedIndexBuffer,
|
||||||
indexBufferOffset: 0
|
indexBufferOffset: 0
|
||||||
)
|
)
|
||||||
self.erasedIndexBuffer = nil
|
|
||||||
self.erasedVertexBuffer = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
final class Photo: @unchecked Sendable, Equatable, Comparable {
|
final class Photo: @unchecked Sendable, Equatable {
|
||||||
var id: UUID = UUID()
|
var id: UUID = UUID()
|
||||||
var size: CGSize
|
var size: CGSize
|
||||||
var origin: CGPoint
|
var origin: CGPoint
|
||||||
@@ -93,6 +93,10 @@ extension Photo {
|
|||||||
static func < (lhs: Photo, rhs: Photo) -> Bool {
|
static func < (lhs: Photo, rhs: Photo) -> Bool {
|
||||||
lhs.createdAt < rhs.createdAt
|
lhs.createdAt < rhs.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func ^= (lhs: Photo, rhs: Photo) -> Bool {
|
||||||
|
lhs == rhs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Photo {
|
extension Photo {
|
||||||
|
|||||||
@@ -34,53 +34,16 @@ class CacheRenderPass: RenderPass {
|
|||||||
cacheTexture = Textures.createCacheTexture(from: renderer, size: size, pixelFormat: view.colorPixelFormat)
|
cacheTexture = Textures.createCacheTexture(from: renderer, size: size, pixelFormat: view.colorPixelFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer) {
|
func draw(into commandBuffer: any MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer) {
|
||||||
guard let descriptor, let strokeRenderPass, let eraserRenderPass, let photoRenderPass else { return }
|
guard let descriptor else { return }
|
||||||
|
|
||||||
copyTexture(on: canvas, with: renderer)
|
// MARK: - Copying texture
|
||||||
|
|
||||||
guard let graphicPipelineState else { return }
|
|
||||||
descriptor.colorAttachments[0].texture = cacheTexture
|
|
||||||
descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 1, green: 1, blue: 1, alpha: 0)
|
|
||||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
|
||||||
descriptor.colorAttachments[0].storeAction = .store
|
|
||||||
|
|
||||||
let graphicContext = canvas.graphicContext
|
|
||||||
if let element = graphicContext.currentElement {
|
|
||||||
switch element {
|
|
||||||
case .stroke(let anyStroke):
|
|
||||||
let stroke = anyStroke.value
|
|
||||||
switch stroke.style {
|
|
||||||
case .eraser:
|
|
||||||
eraserRenderPass.stroke = stroke
|
|
||||||
eraserRenderPass.descriptor = descriptor
|
|
||||||
eraserRenderPass.draw(on: canvas, with: renderer)
|
|
||||||
case .marker:
|
|
||||||
canvas.setGraphicRenderType(.inProgress)
|
|
||||||
strokeRenderPass.stroke = stroke
|
|
||||||
strokeRenderPass.graphicDescriptor = descriptor
|
|
||||||
strokeRenderPass.graphicPipelineState = graphicPipelineState
|
|
||||||
strokeRenderPass.draw(on: canvas, with: renderer)
|
|
||||||
}
|
|
||||||
case .photo(let photo):
|
|
||||||
photoRenderPass.photo = photo
|
|
||||||
photoRenderPass.descriptor = descriptor
|
|
||||||
photoRenderPass.draw(on: canvas, with: renderer)
|
|
||||||
}
|
|
||||||
clearsTexture = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func copyTexture(on canvas: Canvas, with renderer: Renderer) {
|
|
||||||
guard let graphicTexture, let cacheTexture else { return }
|
guard let graphicTexture, let cacheTexture else { return }
|
||||||
guard let cachePipelineState else { return }
|
guard let cachePipelineState else { return }
|
||||||
guard let copyCommandBuffer = renderer.commandQueue.makeCommandBuffer() else {
|
guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let computeEncoder = copyCommandBuffer.makeComputeCommandEncoder() else {
|
computeEncoder.label = "Cache Compute Encoder"
|
||||||
return
|
|
||||||
}
|
|
||||||
computeEncoder.label = label
|
|
||||||
|
|
||||||
computeEncoder.setComputePipelineState(cachePipelineState)
|
computeEncoder.setComputePipelineState(cachePipelineState)
|
||||||
computeEncoder.setTexture(graphicTexture, index: 0)
|
computeEncoder.setTexture(graphicTexture, index: 0)
|
||||||
@@ -93,6 +56,34 @@ class CacheRenderPass: RenderPass {
|
|||||||
)
|
)
|
||||||
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
|
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
|
||||||
computeEncoder.endEncoding()
|
computeEncoder.endEncoding()
|
||||||
copyCommandBuffer.commit()
|
|
||||||
|
// MARK: - Drawing
|
||||||
|
guard let graphicPipelineState else { return }
|
||||||
|
descriptor.colorAttachments[0].texture = cacheTexture
|
||||||
|
descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 1, green: 1, blue: 1, alpha: 0)
|
||||||
|
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||||
|
descriptor.colorAttachments[0].storeAction = .store
|
||||||
|
|
||||||
|
let graphicContext = canvas.graphicContext
|
||||||
|
if let element = graphicContext.currentElement {
|
||||||
|
let elementGroup = ElementGroup(element)
|
||||||
|
switch elementGroup.type {
|
||||||
|
case .stroke:
|
||||||
|
canvas.setGraphicRenderType(.inProgress)
|
||||||
|
strokeRenderPass?.elementGroup = elementGroup
|
||||||
|
strokeRenderPass?.graphicDescriptor = descriptor
|
||||||
|
strokeRenderPass?.graphicPipelineState = graphicPipelineState
|
||||||
|
strokeRenderPass?.draw(into: commandBuffer, on: canvas, with: renderer)
|
||||||
|
case .eraser:
|
||||||
|
eraserRenderPass?.elementGroup = elementGroup
|
||||||
|
eraserRenderPass?.descriptor = descriptor
|
||||||
|
eraserRenderPass?.draw(into: commandBuffer, on: canvas, with: renderer)
|
||||||
|
case .photo:
|
||||||
|
photoRenderPass?.elementGroup = elementGroup
|
||||||
|
photoRenderPass?.descriptor = descriptor
|
||||||
|
photoRenderPass?.draw(into: commandBuffer, on: canvas, with: renderer)
|
||||||
|
}
|
||||||
|
clearsTexture = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class EraserRenderPass: RenderPass {
|
|||||||
var eraserPipelineState: MTLRenderPipelineState?
|
var eraserPipelineState: MTLRenderPipelineState?
|
||||||
var quadPipelineState: MTLComputePipelineState?
|
var quadPipelineState: MTLComputePipelineState?
|
||||||
|
|
||||||
var stroke: (any Stroke)?
|
var elementGroup: ElementGroup?
|
||||||
weak var graphicTexture: MTLTexture?
|
weak var graphicTexture: MTLTexture?
|
||||||
|
|
||||||
init(renderer: Renderer) {
|
init(renderer: Renderer) {
|
||||||
@@ -26,49 +26,24 @@ class EraserRenderPass: RenderPass {
|
|||||||
|
|
||||||
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer) { }
|
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer) { }
|
||||||
|
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer) {
|
func draw(into commandBuffer: any MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer) {
|
||||||
|
guard let elementGroup else { return }
|
||||||
guard let descriptor else { return }
|
guard let descriptor else { return }
|
||||||
|
|
||||||
generateVertexBuffer(on: canvas, with: renderer)
|
// MARK: - Generating vertices
|
||||||
|
guard !elementGroup.isEmpty, let quadPipelineState else { return }
|
||||||
guard let commandBuffer = renderer.commandQueue.makeCommandBuffer() else { return }
|
let eraserStrokes = elementGroup.elements.compactMap { element -> EraserStroke? in
|
||||||
commandBuffer.label = "Eraser Command Buffer"
|
guard case .stroke(let anyStroke) = element else { return nil }
|
||||||
|
return anyStroke.value as? EraserStroke
|
||||||
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }
|
|
||||||
renderEncoder.label = label
|
|
||||||
|
|
||||||
guard let eraserPipelineState else { return }
|
|
||||||
renderEncoder.setRenderPipelineState(eraserPipelineState)
|
|
||||||
|
|
||||||
canvas.setUniformsBuffer(device: renderer.device, renderEncoder: renderEncoder)
|
|
||||||
if let stroke = stroke as? PenStroke {
|
|
||||||
stroke.erase(device: renderer.device, renderEncoder: renderEncoder)
|
|
||||||
} else {
|
|
||||||
stroke?.draw(device: renderer.device, renderEncoder: renderEncoder)
|
|
||||||
}
|
}
|
||||||
|
let quads = eraserStrokes.flatMap { $0.quads }
|
||||||
|
guard !quads.isEmpty else { return }
|
||||||
|
guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else { return }
|
||||||
|
|
||||||
renderEncoder.endEncoding()
|
computeEncoder.label = "Quad Compute Encoder"
|
||||||
commandBuffer.commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func generateVertexBuffer(on canvas: Canvas, with renderer: Renderer) {
|
let quadCount = quads.endIndex
|
||||||
guard let stroke else { return }
|
let quadBuffer = renderer.device.makeBuffer(bytes: quads, length: MemoryLayout<Quad>.stride * quadCount, options: [])
|
||||||
let quadCount: Int
|
|
||||||
var quads: [Quad]
|
|
||||||
if let stroke = stroke as? PenStroke {
|
|
||||||
quads = stroke.getAllErasedQuads()
|
|
||||||
quadCount = quads.endIndex
|
|
||||||
} else {
|
|
||||||
quadCount = stroke.quads.endIndex
|
|
||||||
quads = stroke.quads
|
|
||||||
}
|
|
||||||
guard !quads.isEmpty, let quadPipelineState else { return }
|
|
||||||
guard let quadCommandBuffer = renderer.commandQueue.makeCommandBuffer() else { return }
|
|
||||||
guard let computeEncoder = quadCommandBuffer.makeComputeCommandEncoder() else { return }
|
|
||||||
|
|
||||||
computeEncoder.label = "Quad Render Pass"
|
|
||||||
|
|
||||||
let quadBuffer = renderer.device.makeBuffer(bytes: &quads, length: MemoryLayout<Quad>.stride * quadCount, options: [])
|
|
||||||
let indexBuffer = renderer.device.makeBuffer(length: MemoryLayout<UInt>.stride * quadCount * 6, options: [.cpuCacheModeWriteCombined])
|
let indexBuffer = renderer.device.makeBuffer(length: MemoryLayout<UInt>.stride * quadCount * 6, options: [.cpuCacheModeWriteCombined])
|
||||||
let vertexBuffer = renderer.device.makeBuffer(length: MemoryLayout<QuadVertex>.stride * quadCount * 4, options: [.cpuCacheModeWriteCombined])
|
let vertexBuffer = renderer.device.makeBuffer(length: MemoryLayout<QuadVertex>.stride * quadCount * 4, options: [.cpuCacheModeWriteCombined])
|
||||||
|
|
||||||
@@ -77,20 +52,30 @@ class EraserRenderPass: RenderPass {
|
|||||||
computeEncoder.setBuffer(indexBuffer, offset: 0, index: 1)
|
computeEncoder.setBuffer(indexBuffer, offset: 0, index: 1)
|
||||||
computeEncoder.setBuffer(vertexBuffer, offset: 0, index: 2)
|
computeEncoder.setBuffer(vertexBuffer, offset: 0, index: 2)
|
||||||
|
|
||||||
if let stroke = stroke as? PenStroke {
|
|
||||||
stroke.erasedIndexBuffer = indexBuffer
|
|
||||||
stroke.erasedVertexBuffer = vertexBuffer
|
|
||||||
stroke.erasedQuadCount = quadCount
|
|
||||||
} else {
|
|
||||||
stroke.indexBuffer = indexBuffer
|
|
||||||
stroke.vertexBuffer = vertexBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
let threadsPerGroup = MTLSize(width: 1, height: 1, depth: 1)
|
let threadsPerGroup = MTLSize(width: 1, height: 1, depth: 1)
|
||||||
let numThreadgroups = MTLSize(width: quadCount + 1, height: 1, depth: 1)
|
let numThreadgroups = MTLSize(width: quadCount + 1, height: 1, depth: 1)
|
||||||
computeEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)
|
computeEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)
|
||||||
computeEncoder.endEncoding()
|
computeEncoder.endEncoding()
|
||||||
quadCommandBuffer.commit()
|
|
||||||
quadCommandBuffer.waitUntilCompleted()
|
// MARK: - Rendering eraser
|
||||||
|
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }
|
||||||
|
renderEncoder.label = "Stroke Render Encoder"
|
||||||
|
|
||||||
|
guard let eraserPipelineState else { return }
|
||||||
|
renderEncoder.setRenderPipelineState(eraserPipelineState)
|
||||||
|
|
||||||
|
canvas.setUniformsBuffer(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
|
|
||||||
|
if let indexBuffer {
|
||||||
|
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
||||||
|
renderEncoder.drawIndexedPrimitives(
|
||||||
|
type: .triangle,
|
||||||
|
indexCount: quads.endIndex * 6,
|
||||||
|
indexType: .uint32,
|
||||||
|
indexBuffer: indexBuffer,
|
||||||
|
indexBufferOffset: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
renderEncoder.endEncoding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,94 +33,71 @@ class GraphicRenderPass: RenderPass {
|
|||||||
clearsTexture = true
|
clearsTexture = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer) {
|
func draw(into commandBuffer: any MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer) {
|
||||||
guard let strokeRenderPass, let eraserRenderPass, let photoRenderPass, let photoBackgroundRenderPass else { return }
|
descriptor?.colorAttachments[0].texture = graphicTexture
|
||||||
guard let descriptor else { return }
|
descriptor?.colorAttachments[0].clearColor = MTLClearColor(red: 1, green: 1, blue: 1, alpha: 0)
|
||||||
|
descriptor?.colorAttachments[0].storeAction = .store
|
||||||
guard let graphicPipelineState else { return }
|
|
||||||
guard let graphicTexture else { return }
|
|
||||||
|
|
||||||
descriptor.colorAttachments[0].texture = graphicTexture
|
|
||||||
descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 1, green: 1, blue: 1, alpha: 0)
|
|
||||||
descriptor.colorAttachments[0].storeAction = .store
|
|
||||||
|
|
||||||
let graphicContext = canvas.graphicContext
|
let graphicContext = canvas.graphicContext
|
||||||
if renderer.redrawsGraphicRender {
|
if renderer.redrawsGraphicRender {
|
||||||
canvas.setGraphicRenderType(.finished)
|
canvas.setGraphicRenderType(.finished)
|
||||||
|
var elementGroup: ElementGroup?
|
||||||
|
let start = Date.now.timeIntervalSince1970 * 1000
|
||||||
for _element in graphicContext.tree.search(box: canvas.bounds.box) {
|
for _element in graphicContext.tree.search(box: canvas.bounds.box) {
|
||||||
if graphicContext.previousElement == _element || graphicContext.currentElement == _element {
|
if graphicContext.previousElement == _element || graphicContext.currentElement == _element {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch _element {
|
if elementGroup == nil {
|
||||||
case .stroke(let _stroke):
|
let _elementGroup = ElementGroup(_element)
|
||||||
let stroke = _stroke.value
|
elementGroup = _elementGroup
|
||||||
guard stroke.isVisible(in: canvas.bounds) else { continue }
|
} else {
|
||||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
guard let _elementGroup = elementGroup else { return }
|
||||||
switch stroke.style {
|
if _elementGroup.isSameElement(_element) {
|
||||||
case .eraser:
|
_elementGroup.add(_element)
|
||||||
eraserRenderPass.stroke = stroke
|
} else {
|
||||||
eraserRenderPass.descriptor = descriptor
|
if let elementGroup {
|
||||||
eraserRenderPass.draw(on: canvas, with: renderer)
|
draw(for: elementGroup, into: commandBuffer, on: canvas, with: renderer)
|
||||||
case .marker:
|
}
|
||||||
canvas.setGraphicRenderType(.finished)
|
let _elementGroup = ElementGroup(_element)
|
||||||
strokeRenderPass.stroke = stroke
|
elementGroup = _elementGroup
|
||||||
strokeRenderPass.graphicDescriptor = descriptor
|
|
||||||
strokeRenderPass.graphicPipelineState = graphicPipelineState
|
|
||||||
strokeRenderPass.draw(on: canvas, with: renderer)
|
|
||||||
}
|
}
|
||||||
clearsTexture = false
|
|
||||||
case .photo(let photo):
|
|
||||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let elementGroup {
|
||||||
|
draw(for: elementGroup, into: commandBuffer, on: canvas, with: renderer)
|
||||||
|
}
|
||||||
|
let end = Date.now.timeIntervalSince1970 * 1000
|
||||||
|
NSLog("[Memola] - duration: \(end - start)")
|
||||||
renderer.redrawsGraphicRender = false
|
renderer.redrawsGraphicRender = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if let element = graphicContext.previousElement {
|
if let element = graphicContext.previousElement {
|
||||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
let elementGroup = ElementGroup(element)
|
||||||
switch element {
|
draw(for: elementGroup, into: commandBuffer, on: canvas, with: renderer)
|
||||||
case .stroke(let anyStroke):
|
|
||||||
let stroke = anyStroke.value
|
|
||||||
switch stroke.style {
|
|
||||||
case .eraser:
|
|
||||||
eraserRenderPass.stroke = stroke
|
|
||||||
eraserRenderPass.descriptor = descriptor
|
|
||||||
eraserRenderPass.draw(on: canvas, with: renderer)
|
|
||||||
case .marker:
|
|
||||||
canvas.setGraphicRenderType(.newlyFinished)
|
|
||||||
strokeRenderPass.stroke = stroke
|
|
||||||
strokeRenderPass.graphicDescriptor = descriptor
|
|
||||||
strokeRenderPass.graphicPipelineState = graphicPipelineState
|
|
||||||
strokeRenderPass.draw(on: canvas, with: renderer)
|
|
||||||
}
|
|
||||||
case .photo(let photo):
|
|
||||||
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
|
graphicContext.previousElement = nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let eraserStrokes = graphicContext.eraserStrokes
|
private func draw(for elementGroup: ElementGroup, into commandBuffer: MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer) {
|
||||||
for eraserStroke in eraserStrokes {
|
switch elementGroup.type {
|
||||||
if eraserStroke.finishesSaving {
|
case .stroke:
|
||||||
graphicContext.eraserStrokes.remove(eraserStroke)
|
descriptor?.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||||
continue
|
strokeRenderPass?.elementGroup = elementGroup
|
||||||
}
|
strokeRenderPass?.graphicDescriptor = descriptor
|
||||||
|
strokeRenderPass?.graphicPipelineState = graphicPipelineState
|
||||||
|
strokeRenderPass?.draw(into: commandBuffer, on: canvas, with: renderer)
|
||||||
|
clearsTexture = false
|
||||||
|
case .eraser:
|
||||||
|
descriptor?.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||||
|
eraserRenderPass?.elementGroup = elementGroup
|
||||||
|
eraserRenderPass?.descriptor = descriptor
|
||||||
|
eraserRenderPass?.draw(into: commandBuffer, on: canvas, with: renderer)
|
||||||
|
clearsTexture = false
|
||||||
|
case .photo:
|
||||||
|
descriptor?.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||||
|
photoRenderPass?.elementGroup = elementGroup
|
||||||
|
photoRenderPass?.descriptor = descriptor
|
||||||
|
photoRenderPass?.draw(into: commandBuffer, on: canvas, with: renderer)
|
||||||
|
clearsTexture = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ class PhotoBackgroundRenderPass: RenderPass {
|
|||||||
photoBackgroundTexture = Textures.createPhotoBackgroundTexture(from: renderer, size: size, pixelFormat: renderer.pixelFormat)
|
photoBackgroundTexture = Textures.createPhotoBackgroundTexture(from: renderer, size: size, pixelFormat: renderer.pixelFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func draw(into commandBuffer: any MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer) {
|
func draw(on canvas: Canvas, with renderer: Renderer) {
|
||||||
guard let descriptor else { return }
|
guard let descriptor else { return }
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class PhotoRenderPass: RenderPass {
|
|||||||
var photoPipelineState: MTLRenderPipelineState?
|
var photoPipelineState: MTLRenderPipelineState?
|
||||||
weak var graphicTexture: MTLTexture?
|
weak var graphicTexture: MTLTexture?
|
||||||
|
|
||||||
var photo: Photo?
|
var elementGroup: ElementGroup?
|
||||||
|
|
||||||
init(renderer: Renderer) {
|
init(renderer: Renderer) {
|
||||||
photoPipelineState = PipelineStates.createPhotoPipelineState(from: renderer)
|
photoPipelineState = PipelineStates.createPhotoPipelineState(from: renderer)
|
||||||
@@ -24,11 +24,16 @@ class PhotoRenderPass: RenderPass {
|
|||||||
|
|
||||||
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer) { }
|
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer) { }
|
||||||
|
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer) {
|
func draw(into commandBuffer: any MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer) {
|
||||||
|
guard let elementGroup else { return }
|
||||||
guard let descriptor else { return }
|
guard let descriptor else { return }
|
||||||
|
|
||||||
guard let commandBuffer = renderer.commandQueue.makeCommandBuffer() else { return }
|
guard !elementGroup.isEmpty else { return }
|
||||||
commandBuffer.label = "Photo Command Buffer"
|
|
||||||
|
let photos = elementGroup.elements.compactMap { element -> Photo? in
|
||||||
|
guard case .photo(let photo) = element else { return nil }
|
||||||
|
return photo
|
||||||
|
}
|
||||||
|
|
||||||
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }
|
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }
|
||||||
renderEncoder.label = label
|
renderEncoder.label = label
|
||||||
@@ -37,10 +42,11 @@ class PhotoRenderPass: RenderPass {
|
|||||||
renderEncoder.setRenderPipelineState(photoPipelineState)
|
renderEncoder.setRenderPipelineState(photoPipelineState)
|
||||||
|
|
||||||
canvas.setUniformsBuffer(device: renderer.device, renderEncoder: renderEncoder)
|
canvas.setUniformsBuffer(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
photo?.draw(device: renderer.device, renderEncoder: renderEncoder)
|
|
||||||
|
for photo in photos {
|
||||||
|
photo.draw(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
|
}
|
||||||
|
|
||||||
renderEncoder.endEncoding()
|
renderEncoder.endEncoding()
|
||||||
commandBuffer.commit()
|
|
||||||
commandBuffer.waitUntilCompleted()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class StrokeRenderPass: RenderPass {
|
|||||||
var quadPipelineState: MTLComputePipelineState?
|
var quadPipelineState: MTLComputePipelineState?
|
||||||
weak var graphicPipelineState: MTLRenderPipelineState?
|
weak var graphicPipelineState: MTLRenderPipelineState?
|
||||||
|
|
||||||
var stroke: (any Stroke)?
|
var elementGroup: ElementGroup?
|
||||||
var strokeTexture: MTLTexture?
|
var strokeTexture: MTLTexture?
|
||||||
|
|
||||||
weak var eraserRenderPass: EraserRenderPass?
|
weak var eraserRenderPass: EraserRenderPass?
|
||||||
@@ -34,51 +34,26 @@ class StrokeRenderPass: RenderPass {
|
|||||||
strokeTexture = Textures.createStrokeTexture(from: renderer, size: size, pixelFormat: view.colorPixelFormat)
|
strokeTexture = Textures.createStrokeTexture(from: renderer, size: size, pixelFormat: view.colorPixelFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer) {
|
func draw(into commandBuffer: any MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer) {
|
||||||
|
guard let elementGroup else { return }
|
||||||
guard let descriptor else { return }
|
guard let descriptor else { return }
|
||||||
|
|
||||||
generateVertexBuffer(on: canvas, with: renderer)
|
// MARK: - Generating vertices
|
||||||
|
guard !elementGroup.isEmpty, let quadPipelineState else { return }
|
||||||
guard let strokeTexture else { return }
|
let penStrokes = elementGroup.elements.compactMap { element -> PenStroke? in
|
||||||
descriptor.colorAttachments[0].texture = strokeTexture
|
guard case .stroke(let anyStroke) = element else { return nil }
|
||||||
descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 1, green: 1, blue: 1, alpha: 0)
|
return anyStroke.value as? PenStroke
|
||||||
descriptor.colorAttachments[0].loadAction = .clear
|
|
||||||
descriptor.colorAttachments[0].storeAction = .store
|
|
||||||
|
|
||||||
guard let commandBuffer = renderer.commandQueue.makeCommandBuffer() else { return }
|
|
||||||
commandBuffer.label = "Stroke Command Buffer"
|
|
||||||
|
|
||||||
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }
|
|
||||||
renderEncoder.label = label
|
|
||||||
|
|
||||||
guard let strokePipelineState else { return }
|
|
||||||
renderEncoder.setRenderPipelineState(strokePipelineState)
|
|
||||||
|
|
||||||
canvas.setUniformsBuffer(device: renderer.device, renderEncoder: renderEncoder)
|
|
||||||
stroke?.draw(device: renderer.device, renderEncoder: renderEncoder)
|
|
||||||
renderEncoder.endEncoding()
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
let penStroke = penStrokes.first
|
||||||
|
let quads = penStrokes.flatMap { $0.quads }
|
||||||
|
let erasedQuads = Set(penStrokes.flatMap { $0.eraserStrokes }).flatMap { $0.quads }
|
||||||
|
guard !quads.isEmpty else { return }
|
||||||
|
guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else { return }
|
||||||
|
|
||||||
drawStrokeTexture(on: canvas, with: renderer)
|
computeEncoder.label = "Quad Compute Encoder"
|
||||||
}
|
|
||||||
|
|
||||||
private func generateVertexBuffer(on canvas: Canvas, with renderer: Renderer) {
|
let quadCount = quads.endIndex
|
||||||
guard let stroke, !stroke.isEmpty, let quadPipelineState else { return }
|
let quadBuffer = renderer.device.makeBuffer(bytes: quads, length: MemoryLayout<Quad>.stride * quadCount, options: [])
|
||||||
guard let quadCommandBuffer = renderer.commandQueue.makeCommandBuffer() else { return }
|
|
||||||
guard let computeEncoder = quadCommandBuffer.makeComputeCommandEncoder() else { return }
|
|
||||||
|
|
||||||
computeEncoder.label = "Quad Render Pass"
|
|
||||||
|
|
||||||
let quadCount = stroke.quads.endIndex
|
|
||||||
var quads = stroke.quads
|
|
||||||
let quadBuffer = renderer.device.makeBuffer(bytes: &quads, length: MemoryLayout<Quad>.stride * quadCount, options: [])
|
|
||||||
let indexBuffer = renderer.device.makeBuffer(length: MemoryLayout<UInt>.stride * quadCount * 6, options: [.cpuCacheModeWriteCombined])
|
let indexBuffer = renderer.device.makeBuffer(length: MemoryLayout<UInt>.stride * quadCount * 6, options: [.cpuCacheModeWriteCombined])
|
||||||
let vertexBuffer = renderer.device.makeBuffer(length: MemoryLayout<QuadVertex>.stride * quadCount * 4, options: [.cpuCacheModeWriteCombined])
|
let vertexBuffer = renderer.device.makeBuffer(length: MemoryLayout<QuadVertex>.stride * quadCount * 4, options: [.cpuCacheModeWriteCombined])
|
||||||
|
|
||||||
@@ -87,33 +62,94 @@ class StrokeRenderPass: RenderPass {
|
|||||||
computeEncoder.setBuffer(indexBuffer, offset: 0, index: 1)
|
computeEncoder.setBuffer(indexBuffer, offset: 0, index: 1)
|
||||||
computeEncoder.setBuffer(vertexBuffer, offset: 0, index: 2)
|
computeEncoder.setBuffer(vertexBuffer, offset: 0, index: 2)
|
||||||
|
|
||||||
stroke.indexBuffer = indexBuffer
|
|
||||||
stroke.vertexBuffer = vertexBuffer
|
|
||||||
|
|
||||||
let threadsPerGroup = MTLSize(width: 1, height: 1, depth: 1)
|
let threadsPerGroup = MTLSize(width: 1, height: 1, depth: 1)
|
||||||
let numThreadgroups = MTLSize(width: quadCount + 1, height: 1, depth: 1)
|
let numThreadgroups = MTLSize(width: quadCount + 1, height: 1, depth: 1)
|
||||||
computeEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)
|
computeEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)
|
||||||
computeEncoder.endEncoding()
|
computeEncoder.endEncoding()
|
||||||
quadCommandBuffer.commit()
|
|
||||||
quadCommandBuffer.waitUntilCompleted()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func drawStrokeTexture(on canvas: Canvas, with renderer: Renderer) {
|
// MARK: - Rendering stroke
|
||||||
guard let stroke else { return }
|
guard let strokeTexture else { return }
|
||||||
|
descriptor.colorAttachments[0].texture = strokeTexture
|
||||||
|
descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 1, green: 1, blue: 1, alpha: 0)
|
||||||
|
descriptor.colorAttachments[0].loadAction = .clear
|
||||||
|
descriptor.colorAttachments[0].storeAction = .store
|
||||||
|
|
||||||
|
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }
|
||||||
|
renderEncoder.label = "Stroke Render Encoder"
|
||||||
|
|
||||||
|
guard let strokePipelineState else { return }
|
||||||
|
renderEncoder.setRenderPipelineState(strokePipelineState)
|
||||||
|
|
||||||
|
canvas.setUniformsBuffer(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
|
|
||||||
|
if let penStyle = penStroke?.penStyle, let indexBuffer {
|
||||||
|
if penStyle.textureName != nil {
|
||||||
|
let texture = penStyle.loadTexture(on: renderer.device)
|
||||||
|
renderEncoder.setFragmentTexture(texture, index: 0)
|
||||||
|
}
|
||||||
|
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
||||||
|
renderEncoder.drawIndexedPrimitives(
|
||||||
|
type: .triangle,
|
||||||
|
indexCount: quads.endIndex * 6,
|
||||||
|
indexType: .uint32,
|
||||||
|
indexBuffer: indexBuffer,
|
||||||
|
indexBufferOffset: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
renderEncoder.endEncoding()
|
||||||
|
|
||||||
|
// MARK: Erasing path
|
||||||
|
if let eraserPipelineState = eraserRenderPass?.eraserPipelineState, !erasedQuads.isEmpty {
|
||||||
|
guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else { return }
|
||||||
|
|
||||||
|
computeEncoder.label = "Erased Quad Compute Encoder"
|
||||||
|
|
||||||
|
let erasedQuadCount = erasedQuads.endIndex
|
||||||
|
let erasedQuadBuffer = renderer.device.makeBuffer(bytes: erasedQuads, length: MemoryLayout<Quad>.stride * erasedQuadCount, options: [])
|
||||||
|
let erasedIndexBuffer = renderer.device.makeBuffer(length: MemoryLayout<UInt>.stride * erasedQuadCount * 6, options: [.cpuCacheModeWriteCombined])
|
||||||
|
let erasedVertexBuffer = renderer.device.makeBuffer(length: MemoryLayout<QuadVertex>.stride * erasedQuadCount * 4, options: [.cpuCacheModeWriteCombined])
|
||||||
|
|
||||||
|
computeEncoder.setComputePipelineState(quadPipelineState)
|
||||||
|
computeEncoder.setBuffer(erasedQuadBuffer, offset: 0, index: 0)
|
||||||
|
computeEncoder.setBuffer(erasedIndexBuffer, offset: 0, index: 1)
|
||||||
|
computeEncoder.setBuffer(erasedVertexBuffer, offset: 0, index: 2)
|
||||||
|
|
||||||
|
let threadsPerGroup = MTLSize(width: 1, height: 1, depth: 1)
|
||||||
|
let numThreadgroups = MTLSize(width: erasedQuadCount + 1, height: 1, depth: 1)
|
||||||
|
computeEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)
|
||||||
|
computeEncoder.endEncoding()
|
||||||
|
|
||||||
|
descriptor.colorAttachments[0].loadAction = .load
|
||||||
|
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }
|
||||||
|
renderEncoder.label = "Stroke Eraser Render Encoder"
|
||||||
|
|
||||||
|
renderEncoder.setRenderPipelineState(eraserPipelineState)
|
||||||
|
|
||||||
|
canvas.setUniformsBuffer(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
|
if let erasedIndexBuffer {
|
||||||
|
renderEncoder.setVertexBuffer(erasedVertexBuffer, offset: 0, index: 0)
|
||||||
|
renderEncoder.drawIndexedPrimitives(
|
||||||
|
type: .triangle,
|
||||||
|
indexCount: erasedQuadCount * 6,
|
||||||
|
indexType: .uint32,
|
||||||
|
indexBuffer: erasedIndexBuffer,
|
||||||
|
indexBufferOffset: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
renderEncoder.endEncoding()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Drawing on graphic texture
|
||||||
guard let graphicDescriptor, let graphicPipelineState else { return }
|
guard let graphicDescriptor, let graphicPipelineState else { return }
|
||||||
|
|
||||||
guard let commandBuffer = renderer.commandQueue.makeCommandBuffer() else { return }
|
|
||||||
commandBuffer.label = "Graphic Command Buffer"
|
|
||||||
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: graphicDescriptor) else { return }
|
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: graphicDescriptor) else { return }
|
||||||
renderEncoder.label = "Graphic Render Pass"
|
renderEncoder.label = "Stroke Graphic Render Encoder"
|
||||||
renderEncoder.setRenderPipelineState(graphicPipelineState)
|
renderEncoder.setRenderPipelineState(graphicPipelineState)
|
||||||
|
|
||||||
renderEncoder.setFragmentTexture(strokeTexture, index: 0)
|
renderEncoder.setFragmentTexture(strokeTexture, index: 0)
|
||||||
var uniforms = GraphicUniforms(color: stroke.color)
|
var uniforms = GraphicUniforms(color: elementGroup.getPenColor() ?? [])
|
||||||
let uniformsBuffer = renderer.device.makeBuffer(bytes: &uniforms, length: MemoryLayout<Uniforms>.size)
|
let uniformsBuffer = renderer.device.makeBuffer(bytes: &uniforms, length: MemoryLayout<Uniforms>.size)
|
||||||
renderEncoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 11)
|
renderEncoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 11)
|
||||||
canvas.renderGraphic(device: renderer.device, renderEncoder: renderEncoder)
|
canvas.renderGraphic(device: renderer.device, renderEncoder: renderEncoder)
|
||||||
renderEncoder.endEncoding()
|
renderEncoder.endEncoding()
|
||||||
commandBuffer.commit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,18 +29,14 @@ class ViewPortRenderPass: RenderPass {
|
|||||||
|
|
||||||
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer) { }
|
func resize(on view: MTKView, to size: CGSize, with renderer: Renderer) { }
|
||||||
|
|
||||||
func draw(on canvas: Canvas, with renderer: Renderer) {
|
func draw(into commandBuffer: any MTLCommandBuffer, on canvas: Canvas, with renderer: Renderer) {
|
||||||
guard let descriptor else {
|
guard let descriptor else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let commandBuffer = renderer.commandQueue.makeCommandBuffer() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
commandBuffer.label = "View Port Command Buffer"
|
|
||||||
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
|
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
renderEncoder.label = label
|
renderEncoder.label = "View Port Render Encoder"
|
||||||
|
|
||||||
guard let gridPipelineState else { return }
|
guard let gridPipelineState else { return }
|
||||||
renderEncoder.setRenderPipelineState(gridPipelineState)
|
renderEncoder.setRenderPipelineState(gridPipelineState)
|
||||||
|
|||||||
Reference in New Issue
Block a user