From 32af33f617a4a14eafedf00c5db2c41b98e5521d Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Tue, 25 Jun 2024 17:01:10 +0700 Subject: [PATCH 1/3] feat: remove guard condition --- Memola/Canvas/Contexts/GraphicContext.swift | 3 --- Memola/Canvas/Contexts/GridContext.swift | 3 --- Memola/Canvas/Contexts/ViewPortContext.swift | 3 --- 3 files changed, 9 deletions(-) diff --git a/Memola/Canvas/Contexts/GraphicContext.swift b/Memola/Canvas/Contexts/GraphicContext.swift index 8abe1c1..101cd77 100644 --- a/Memola/Canvas/Contexts/GraphicContext.swift +++ b/Memola/Canvas/Contexts/GraphicContext.swift @@ -174,9 +174,6 @@ extension GraphicContext { extension GraphicContext: Drawable { func prepare(device: MTLDevice) { - guard vertexBuffer == nil else { - return - } vertexCount = vertices.count vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout.stride, options: []) } diff --git a/Memola/Canvas/Contexts/GridContext.swift b/Memola/Canvas/Contexts/GridContext.swift index 1535f6f..c53b0aa 100644 --- a/Memola/Canvas/Contexts/GridContext.swift +++ b/Memola/Canvas/Contexts/GridContext.swift @@ -30,9 +30,6 @@ class GridContext { extension GridContext: Drawable { func prepare(device: MTLDevice) { - guard vertexBuffer == nil else { - return - } vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout.stride, options: []) } diff --git a/Memola/Canvas/Contexts/ViewPortContext.swift b/Memola/Canvas/Contexts/ViewPortContext.swift index 3a00db0..f74e6b5 100644 --- a/Memola/Canvas/Contexts/ViewPortContext.swift +++ b/Memola/Canvas/Contexts/ViewPortContext.swift @@ -36,9 +36,6 @@ class ViewPortContext { extension ViewPortContext: Drawable { func prepare(device: MTLDevice) { - guard vertexBuffer == nil else { - return - } vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout.stride, options: []) } From de71b567acecc74e2cc24dd06a7a329c861adce9 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Tue, 25 Jun 2024 20:26:58 +0700 Subject: [PATCH 2/3] feat: add grid option --- Memola.xcodeproj/project.pbxproj | 36 +++++++++++---- .../Buffers/Vertices/LineGridVertex.swift | 13 ++++++ ...GridVertex.swift => PointGridVertex.swift} | 6 +-- Memola/Canvas/Contexts/LineGridContext.swift | 45 +++++++++++++++++++ ...idContext.swift => PointGridContext.swift} | 12 ++--- Memola/Canvas/Core/Canvas.swift | 37 +++++++++++++-- Memola/Canvas/Core/PipelineStates.swift | 29 ++++++++++-- Memola/Canvas/Grid/GridMode.swift | 38 ++++++++++++++++ .../RenderPasses/ViewPortRenderPass.swift | 21 ++++++--- Memola/Canvas/Shaders/Grid.metal | 35 ++++++++++++--- .../ViewController/CanvasViewController.swift | 15 ++++++- Memola/Features/Memo/Memo/MemoView.swift | 2 +- Memola/Features/Memo/Toolbar/Toolbar.swift | 28 +++++++++++- Memola/Features/Memos/MemosView.swift | 1 + Memola/Persistence/Objects/CanvasObject.swift | 1 + .../MemolaModel.xcdatamodel/contents | 1 + 16 files changed, 279 insertions(+), 41 deletions(-) create mode 100644 Memola/Canvas/Buffers/Vertices/LineGridVertex.swift rename Memola/Canvas/Buffers/Vertices/{GridVertex.swift => PointGridVertex.swift} (76%) create mode 100644 Memola/Canvas/Contexts/LineGridContext.swift rename Memola/Canvas/Contexts/{GridContext.swift => PointGridContext.swift} (75%) create mode 100644 Memola/Canvas/Grid/GridMode.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index be5032a..e57b134 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -31,6 +31,9 @@ EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; }; EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; }; EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; }; + EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */; }; + EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */; }; + EC8F54B02C2AF5E9001C7C74 /* LineGridContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */; }; EC9AB09F2C1401A40076AF58 /* EraserObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9AB09E2C1401A40076AF58 /* EraserObject.swift */; }; ECA7387A2BE5EF0400A4542E /* MemosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738792BE5EF0400A4542E /* MemosView.swift */; }; ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7387C2BE5EF4B00A4542E /* MemoView.swift */; }; @@ -44,7 +47,7 @@ ECA738932BE6011100A4542E /* Stroke.metal in Sources */ = {isa = PBXBuildFile; fileRef = ECA738922BE6011100A4542E /* Stroke.metal */; }; ECA738952BE6012D00A4542E /* ViewPort.metal in Sources */ = {isa = PBXBuildFile; fileRef = ECA738942BE6012D00A4542E /* ViewPort.metal */; }; ECA738972BE6014200A4542E /* Graphic.metal in Sources */ = {isa = PBXBuildFile; fileRef = ECA738962BE6014200A4542E /* Graphic.metal */; }; - ECA7389C2BE601AF00A4542E /* GridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389B2BE601AF00A4542E /* GridVertex.swift */; }; + ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */; }; ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389D2BE601CB00A4542E /* QuadVertex.swift */; }; ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389F2BE601E400A4542E /* ViewPortVertex.swift */; }; ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A22BE6020A00A4542E /* CGFloat++.swift */; }; @@ -64,7 +67,7 @@ ECA738C62BE60E9D00A4542E /* EraserPenStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738C52BE60E9D00A4542E /* EraserPenStyle.swift */; }; ECA738C92BE60EF700A4542E /* GraphicContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738C82BE60EF700A4542E /* GraphicContext.swift */; }; ECA738CB2BE60F1900A4542E /* ViewPortContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738CA2BE60F1900A4542E /* ViewPortContext.swift */; }; - ECA738CD2BE60F2F00A4542E /* GridContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738CC2BE60F2F00A4542E /* GridContext.swift */; }; + ECA738CD2BE60F2F00A4542E /* PointGridContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738CC2BE60F2F00A4542E /* PointGridContext.swift */; }; ECA738D22BE60F7B00A4542E /* PenStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738D12BE60F7B00A4542E /* PenStroke.swift */; }; ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738D32BE60F9100A4542E /* StrokeGenerator.swift */; }; ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738D62BE60FC100A4542E /* SolidPointStrokeGenerator.swift */; }; @@ -136,6 +139,9 @@ EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = ""; }; EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridMode.swift; sourceTree = ""; }; + EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridVertex.swift; sourceTree = ""; }; + EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridContext.swift; sourceTree = ""; }; EC9AB09E2C1401A40076AF58 /* EraserObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserObject.swift; sourceTree = ""; }; ECA738792BE5EF0400A4542E /* MemosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemosView.swift; sourceTree = ""; }; ECA7387C2BE5EF4B00A4542E /* MemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoView.swift; sourceTree = ""; }; @@ -149,7 +155,7 @@ ECA738922BE6011100A4542E /* Stroke.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Stroke.metal; sourceTree = ""; }; ECA738942BE6012D00A4542E /* ViewPort.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = ViewPort.metal; sourceTree = ""; }; ECA738962BE6014200A4542E /* Graphic.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Graphic.metal; sourceTree = ""; }; - ECA7389B2BE601AF00A4542E /* GridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridVertex.swift; sourceTree = ""; }; + ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointGridVertex.swift; sourceTree = ""; }; ECA7389D2BE601CB00A4542E /* QuadVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadVertex.swift; sourceTree = ""; }; ECA7389F2BE601E400A4542E /* ViewPortVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewPortVertex.swift; sourceTree = ""; }; ECA738A22BE6020A00A4542E /* CGFloat++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat++.swift"; sourceTree = ""; }; @@ -169,7 +175,7 @@ ECA738C52BE60E9D00A4542E /* EraserPenStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserPenStyle.swift; sourceTree = ""; }; ECA738C82BE60EF700A4542E /* GraphicContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicContext.swift; sourceTree = ""; }; ECA738CA2BE60F1900A4542E /* ViewPortContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewPortContext.swift; sourceTree = ""; }; - ECA738CC2BE60F2F00A4542E /* GridContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridContext.swift; sourceTree = ""; }; + ECA738CC2BE60F2F00A4542E /* PointGridContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointGridContext.swift; sourceTree = ""; }; ECA738D12BE60F7B00A4542E /* PenStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenStroke.swift; sourceTree = ""; }; ECA738D32BE60F9100A4542E /* StrokeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeGenerator.swift; sourceTree = ""; }; ECA738D62BE60FC100A4542E /* SolidPointStrokeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolidPointStrokeGenerator.swift; sourceTree = ""; }; @@ -376,6 +382,14 @@ path = "Preview Content"; sourceTree = ""; }; + EC8F54AA2C2ACD9D001C7C74 /* Grid */ = { + isa = PBXGroup; + children = ( + EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */, + ); + path = Grid; + sourceTree = ""; + }; ECA738762BE5EE4E00A4542E /* App */ = { isa = PBXGroup; children = ( @@ -415,6 +429,7 @@ ECA7387E2BE5FE4200A4542E /* Canvas */ = { isa = PBXGroup; children = ( + EC8F54AA2C2ACD9D001C7C74 /* Grid */, ECD12A872C19EF8700B96E12 /* Elements */, EC2BEBF22C0F5FE1005DB0AF /* RTree */, ECA738812BE5FEEE00A4542E /* Abstracts */, @@ -494,10 +509,11 @@ ECA7389A2BE6019700A4542E /* Vertices */ = { isa = PBXGroup; children = ( - ECA7389B2BE601AF00A4542E /* GridVertex.swift */, + ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */, ECA7389D2BE601CB00A4542E /* QuadVertex.swift */, ECA7389F2BE601E400A4542E /* ViewPortVertex.swift */, ECD12A942C1B1FA200B96E12 /* PhotoVertex.swift */, + EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */, ); path = Vertices; sourceTree = ""; @@ -595,7 +611,8 @@ children = ( ECA738C82BE60EF700A4542E /* GraphicContext.swift */, ECA738CA2BE60F1900A4542E /* ViewPortContext.swift */, - ECA738CC2BE60F2F00A4542E /* GridContext.swift */, + ECA738CC2BE60F2F00A4542E /* PointGridContext.swift */, + EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */, ); path = Contexts; sourceTree = ""; @@ -854,10 +871,12 @@ ECA738AD2BE60CC600A4542E /* DrawingView.swift in Sources */, EC1B783D2BFA0AC9005A34E2 /* Toolbar.swift in Sources */, ECA738E02BE610B900A4542E /* EraserRenderPass.swift in Sources */, + EC8F54B02C2AF5E9001C7C74 /* LineGridContext.swift in Sources */, EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */, ECA738912BE600F500A4542E /* Cache.metal in Sources */, - ECA7389C2BE601AF00A4542E /* GridVertex.swift in Sources */, + ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */, ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */, + EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */, ECA738E62BE611FD00A4542E /* CGRect++.swift in Sources */, EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */, ECFA15262BEF224900455818 /* StrokeObject.swift in Sources */, @@ -870,7 +889,7 @@ ECA738AA2BE6026D00A4542E /* Uniforms.swift in Sources */, ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */, ECA738DA2BE60FF100A4542E /* CacheRenderPass.swift in Sources */, - ECA738CD2BE60F2F00A4542E /* GridContext.swift in Sources */, + ECA738CD2BE60F2F00A4542E /* PointGridContext.swift in Sources */, ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */, ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */, EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */, @@ -934,6 +953,7 @@ ECA738C12BE60E5300A4542E /* PenStyle.swift in Sources */, ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */, ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */, + EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */, EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */, EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */, ECC995A52C1EB4CC00B2699A /* Data++.swift in Sources */, diff --git a/Memola/Canvas/Buffers/Vertices/LineGridVertex.swift b/Memola/Canvas/Buffers/Vertices/LineGridVertex.swift new file mode 100644 index 0000000..42786b7 --- /dev/null +++ b/Memola/Canvas/Buffers/Vertices/LineGridVertex.swift @@ -0,0 +1,13 @@ +// +// LineGridVertex.swift +// Memola +// +// Created by Dscyre Scotti on 6/25/24. +// + +import MetalKit +import Foundation + +struct LineGridVertex { + var position: vector_float4 +} diff --git a/Memola/Canvas/Buffers/Vertices/GridVertex.swift b/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift similarity index 76% rename from Memola/Canvas/Buffers/Vertices/GridVertex.swift rename to Memola/Canvas/Buffers/Vertices/PointGridVertex.swift index 646b297..4861397 100644 --- a/Memola/Canvas/Buffers/Vertices/GridVertex.swift +++ b/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift @@ -1,5 +1,5 @@ // -// GridVertex.swift +// PointGridVertex.swift // Memola // // Created by Dscyre Scotti on 5/4/24. @@ -8,12 +8,12 @@ import MetalKit import Foundation -struct GridVertex { +struct PointGridVertex { var position: vector_float4 var pointSize: Float = 10 } -extension GridVertex { +extension PointGridVertex { init(x: CGFloat, y: CGFloat) { self.position = [x.float, y.float, 0, 1] } diff --git a/Memola/Canvas/Contexts/LineGridContext.swift b/Memola/Canvas/Contexts/LineGridContext.swift new file mode 100644 index 0000000..4dc9b09 --- /dev/null +++ b/Memola/Canvas/Contexts/LineGridContext.swift @@ -0,0 +1,45 @@ +// +// LineGridContext.swift +// Memola +// +// Created by Dscyre Scotti on 6/25/24. +// + +import MetalKit +import Foundation + +class LineGridContext { + var vertices: [LineGridVertex] = [] + var vertexCount: Int = 0 + var vertexBuffer: MTLBuffer? + + init() { + generateVertices() + } + + func generateVertices() { + let steps = stride(from: -10, through: 110, by: 0.25) + for y in steps { + vertices.append(LineGridVertex(position: [-10, Float(y), 0, 0])) + vertices.append(LineGridVertex(position: [110, Float(y), 0, 0])) + } + for x in steps { + vertices.append(LineGridVertex(position: [Float(x), -10, 0, 0])) + vertices.append(LineGridVertex(position: [Float(x), 110, 0, 0])) + } + vertexCount = vertices.count + } +} + +extension LineGridContext: Drawable { + func prepare(device: MTLDevice) { + vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout.stride, options: []) + } + + func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) { + guard vertexCount > .zero else { return } + prepare(device: device) + renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) + renderEncoder.drawPrimitives(type: .line, vertexStart: 0, vertexCount: vertexCount) + } +} diff --git a/Memola/Canvas/Contexts/GridContext.swift b/Memola/Canvas/Contexts/PointGridContext.swift similarity index 75% rename from Memola/Canvas/Contexts/GridContext.swift rename to Memola/Canvas/Contexts/PointGridContext.swift index c53b0aa..0f94681 100644 --- a/Memola/Canvas/Contexts/GridContext.swift +++ b/Memola/Canvas/Contexts/PointGridContext.swift @@ -1,5 +1,5 @@ // -// GridContext.swift +// PointGridContext.swift // Memola // // Created by Dscyre Scotti on 5/4/24. @@ -8,8 +8,8 @@ import MetalKit import Foundation -class GridContext { - var vertices: [GridVertex] = [] +class PointGridContext { + var vertices: [PointGridVertex] = [] var vertexCount: Int = 0 var vertexBuffer: MTLBuffer? @@ -21,16 +21,16 @@ class GridContext { let steps = stride(from: -10, through: 110, by: 0.25) for y in steps { for x in steps { - vertices.append(GridVertex(x: CGFloat(x), y: CGFloat(y))) + vertices.append(PointGridVertex(x: CGFloat(x), y: CGFloat(y))) } } vertexCount = vertices.count } } -extension GridContext: Drawable { +extension PointGridContext: Drawable { func prepare(device: MTLDevice) { - vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout.stride, options: []) + vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout.stride, options: []) } func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) { diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index 7c6d07e..03b52aa 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -13,8 +13,10 @@ import Foundation final class Canvas: ObservableObject, Identifiable, @unchecked Sendable { let size: CGSize let canvasID: NSManagedObjectID + var object: CanvasObject? - let gridContext = GridContext() + let pointGridContext = PointGridContext() + let lineGridContext = LineGridContext() var graphicContext = GraphicContext() let viewPortContext = ViewPortContext() @@ -31,11 +33,14 @@ final class Canvas: ObservableObject, Identifiable, @unchecked Sendable { @Published var zoomScale: CGFloat = .zero @Published var locksCanvas: Bool = false + @Published var gridMode: GridMode = .point + let zoomPublisher = PassthroughSubject() - init(size: CGSize, canvasID: NSManagedObjectID) { + init(size: CGSize, canvasID: NSManagedObjectID, gridMode: Int16) { self.size = size self.canvasID = canvasID + self.gridMode = GridMode(rawValue: gridMode) ?? .point } var hasValidStroke: Bool { @@ -56,6 +61,7 @@ extension Canvas { guard let canvas = context.object(with: canvasID) as? CanvasObject else { return } + self?.object = canvas let graphicContext = canvas.graphicContext self?.graphicContext.object = graphicContext self?.graphicContext.loadStrokes(bounds) @@ -103,6 +109,18 @@ extension Canvas { } } +// MARK: - Grid Mode +extension Canvas { + func setGridMode(_ gridMode: GridMode) { + guard self.gridMode != gridMode else { return } + self.gridMode = gridMode + withPersistence(\.backgroundContext) { [weak object] context in + object?.gridMode = gridMode.rawValue + try context.saveIfNeeded() + } + } +} + // MARK: - Stroke extension Canvas { func beginTouch(at point: CGPoint, pen: Pen) -> any Stroke { @@ -135,7 +153,7 @@ extension Canvas { // MARK: - Rendering extension Canvas { - func renderGrid(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) { + func renderPointGrid(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) { var uniforms = GridUniforms( ratio: size.width.float / 100, zoom: zoomScale.float, @@ -143,7 +161,18 @@ extension Canvas { ) uniformsBuffer = device.makeBuffer(bytes: &uniforms, length: MemoryLayout.size) renderEncoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 11) - gridContext.draw(device: device, renderEncoder: renderEncoder) + pointGridContext.draw(device: device, renderEncoder: renderEncoder) + } + + func renderLineGrid(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) { + var uniforms = GridUniforms( + ratio: size.width.float / 100, + zoom: zoomScale.float, + transform: transform + ) + uniformsBuffer = device.makeBuffer(bytes: &uniforms, length: MemoryLayout.size) + renderEncoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 11) + lineGridContext.draw(device: device, renderEncoder: renderEncoder) } func renderGraphic(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) { diff --git a/Memola/Canvas/Core/PipelineStates.swift b/Memola/Canvas/Core/PipelineStates.swift index 01af9dd..b4e02f3 100644 --- a/Memola/Canvas/Core/PipelineStates.swift +++ b/Memola/Canvas/Core/PipelineStates.swift @@ -9,14 +9,35 @@ import MetalKit import Foundation struct PipelineStates { - static func createGridPipelineState(from renderer: Renderer, pixelFormat: MTLPixelFormat? = nil) -> MTLRenderPipelineState? { + static func createPointGridPipelineState(from renderer: Renderer, pixelFormat: MTLPixelFormat? = nil) -> MTLRenderPipelineState? { let device = renderer.device let library = renderer.library let pipelineDescriptor = MTLRenderPipelineDescriptor() - pipelineDescriptor.vertexFunction = library.makeFunction(name: "vertex_grid") - pipelineDescriptor.fragmentFunction = library.makeFunction(name: "fragment_grid") + pipelineDescriptor.vertexFunction = library.makeFunction(name: "vertex_point_grid") + pipelineDescriptor.fragmentFunction = library.makeFunction(name: "fragment_point_grid") pipelineDescriptor.colorAttachments[0].pixelFormat = pixelFormat ?? renderer.pixelFormat - pipelineDescriptor.label = "Grid Pipeline State" + pipelineDescriptor.label = "Point Grid Pipeline State" + + let attachment = pipelineDescriptor.colorAttachments[0] + attachment?.isBlendingEnabled = true + attachment?.rgbBlendOperation = .add + attachment?.sourceRGBBlendFactor = .one + attachment?.destinationRGBBlendFactor = .oneMinusSourceAlpha + attachment?.alphaBlendOperation = .add + attachment?.sourceAlphaBlendFactor = .sourceAlpha + attachment?.destinationAlphaBlendFactor = .oneMinusSourceAlpha + + return try? device.makeRenderPipelineState(descriptor: pipelineDescriptor) + } + + static func createLineGridPipelineState(from renderer: Renderer, pixelFormat: MTLPixelFormat? = nil) -> MTLRenderPipelineState? { + let device = renderer.device + let library = renderer.library + let pipelineDescriptor = MTLRenderPipelineDescriptor() + pipelineDescriptor.vertexFunction = library.makeFunction(name: "vertex_line_grid") + pipelineDescriptor.fragmentFunction = library.makeFunction(name: "fragment_line_grid") + pipelineDescriptor.colorAttachments[0].pixelFormat = pixelFormat ?? renderer.pixelFormat + pipelineDescriptor.label = "Line Grid Pipeline State" let attachment = pipelineDescriptor.colorAttachments[0] attachment?.isBlendingEnabled = true diff --git a/Memola/Canvas/Grid/GridMode.swift b/Memola/Canvas/Grid/GridMode.swift new file mode 100644 index 0000000..063b390 --- /dev/null +++ b/Memola/Canvas/Grid/GridMode.swift @@ -0,0 +1,38 @@ +// +// GridMode.swift +// Memola +// +// Created by Dscyre Scotti on 6/25/24. +// + +import Foundation + +enum GridMode: Int16, Equatable { + case none + case point + case line + + var title: String { + switch self { + case .none: + return "No Grid" + case .point: + return "Point Grid" + case .line: + return "Line Grid" + } + } + + var icon: String { + switch self { + case .none: + return "square.slash" + case .point: + return "circle.grid.3x3.fill" + case .line: + return "squareshape.split.3x3" + } + } + + static let all: [GridMode] = [.none, .point, line] +} diff --git a/Memola/Canvas/RenderPasses/ViewPortRenderPass.swift b/Memola/Canvas/RenderPasses/ViewPortRenderPass.swift index c4246c7..6a25ea3 100644 --- a/Memola/Canvas/RenderPasses/ViewPortRenderPass.swift +++ b/Memola/Canvas/RenderPasses/ViewPortRenderPass.swift @@ -12,7 +12,8 @@ class ViewPortRenderPass: RenderPass { var label: String { "View Port Render Pass"} var descriptor: MTLRenderPassDescriptor? - var gridPipelineState: MTLRenderPipelineState? + var pointGridPipelineState: MTLRenderPipelineState? + var lineGridPipelineState: MTLRenderPipelineState? var viewPortPipelineState: MTLRenderPipelineState? var viewPortUpdatePipelineState: MTLRenderPipelineState? @@ -22,7 +23,8 @@ class ViewPortRenderPass: RenderPass { weak var view: MTKView? init(renderer: Renderer) { - gridPipelineState = PipelineStates.createGridPipelineState(from: renderer) + pointGridPipelineState = PipelineStates.createPointGridPipelineState(from: renderer) + lineGridPipelineState = PipelineStates.createLineGridPipelineState(from: renderer) viewPortPipelineState = PipelineStates.createViewPortPipelineState(from: renderer) viewPortUpdatePipelineState = PipelineStates.createViewPortPipelineState(from: renderer, isUpdate: true) } @@ -38,9 +40,18 @@ class ViewPortRenderPass: RenderPass { } renderEncoder.label = "View Port Render Encoder" - guard let gridPipelineState else { return } - renderEncoder.setRenderPipelineState(gridPipelineState) - canvas.renderGrid(device: renderer.device, renderEncoder: renderEncoder) + switch canvas.gridMode { + case .none: + break + case .point: + guard let pointGridPipelineState else { return } + renderEncoder.setRenderPipelineState(pointGridPipelineState) + canvas.renderPointGrid(device: renderer.device, renderEncoder: renderEncoder) + case .line: + guard let lineGridPipelineState else { return } + renderEncoder.setRenderPipelineState(lineGridPipelineState) + canvas.renderLineGrid(device: renderer.device, renderEncoder: renderEncoder) + } if renderer.updatesViewPort { guard let viewPortUpdatePipelineState else { diff --git a/Memola/Canvas/Shaders/Grid.metal b/Memola/Canvas/Shaders/Grid.metal index 5e6b80d..7d08380 100644 --- a/Memola/Canvas/Shaders/Grid.metal +++ b/Memola/Canvas/Shaders/Grid.metal @@ -8,33 +8,37 @@ #include using namespace metal; -struct Vertex { +struct PointVertex { float4 position [[position]]; float pointSize [[point_size]]; }; +struct LineVertex { + float4 position [[position]]; +}; + struct Uniforms { float ratio; float zoom; float4x4 transform; }; -vertex Vertex vertex_grid( - constant Vertex *vertices [[buffer(0)]], +vertex PointVertex vertex_point_grid( + constant PointVertex *vertices [[buffer(0)]], constant Uniforms &uniforms [[buffer(11)]], uint vertexId [[vertex_id]] ) { - Vertex _vertex = vertices[vertexId]; + PointVertex _vertex = vertices[vertexId]; float x = _vertex.position.x * uniforms.ratio; float y = _vertex.position.y * uniforms.ratio; float4 position = float4(x, y, 0, 1); _vertex.position = uniforms.transform * position; - _vertex.pointSize = 10 * uniforms.zoom / 12; + _vertex.pointSize = 8 * uniforms.zoom / 12; return _vertex; } -fragment float4 fragment_grid( - Vertex _vertex [[stage_in]], +fragment float4 fragment_point_grid( + PointVertex _vertex [[stage_in]], float2 pointCoord [[point_coord]] ) { float dist = length(pointCoord - float2(0.5)); @@ -42,3 +46,20 @@ fragment float4 fragment_grid( color.a = 1.0 - smoothstep(0.4, 0.5, dist); return color; } + +vertex LineVertex vertex_line_grid( + constant LineVertex *vertices [[buffer(0)]], + constant Uniforms &uniforms [[buffer(11)]], + uint vertexId [[vertex_id]] +) { + LineVertex _vertex = vertices[vertexId]; + float x = _vertex.position.x * uniforms.ratio; + float y = _vertex.position.y * uniforms.ratio; + float4 position = float4(x, y, 0, 1); + _vertex.position = uniforms.transform * position; + return _vertex; +} + +fragment float4 fragment_line_grid() { + return float4(0.752, 0.752, 0.752, 1); +} diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 6b80f14..167f97f 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -161,18 +161,22 @@ extension CanvasViewController { self?.canvasStateChanged(state) } .store(in: &cancellables) - canvas.zoomPublisher .sink { [weak self] zoomScale in self?.zoomChanged(zoomScale) } .store(in: &cancellables) - canvas.$locksCanvas .sink { [weak self] state in self?.lockModeChanged(state) } .store(in: &cancellables) + canvas.$gridMode + .delay(for: .milliseconds(100), scheduler: DispatchQueue.main) + .sink { [weak self] mode in + self?.gridModeChanged(mode) + } + .store(in: &cancellables) tool.$selectedPen .sink { [weak self] pen in @@ -364,6 +368,13 @@ extension CanvasViewController { func lockModeChanged(_ state: Bool) { scrollView.pinchGestureRecognizer?.isEnabled = !state } + + func gridModeChanged(_ mode: GridMode) { + drawingView.disableUserInteraction() + renderer.resize(on: renderView, to: renderView.drawableSize) + renderView.draw() + drawingView.enableUserInteraction() + } } extension CanvasViewController { diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 528abd8..afc3b56 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -23,7 +23,7 @@ struct MemoView: View { self.memo = memo self.title = memo.title self._tool = StateObject(wrappedValue: Tool(object: memo.tool)) - self._canvas = StateObject(wrappedValue: Canvas(size: memo.canvas.size, canvasID: memo.canvas.objectID)) + self._canvas = StateObject(wrappedValue: Canvas(size: memo.canvas.size, canvasID: memo.canvas.objectID, gridMode: memo.canvas.gridMode)) } var body: some View { diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index 15d2546..3139d53 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -50,8 +50,9 @@ struct Toolbar: View { if !canvas.locksCanvas { elementTool } - Group { + HStack(spacing: 5) { if !canvas.locksCanvas { + gridModeControl historyControl } } @@ -263,6 +264,31 @@ struct Toolbar: View { .transition(.move(edge: .top).combined(with: .blurReplace)) } + var gridModeControl: some View { + Menu { + ForEach(GridMode.all, id: \.self) { mode in + Button { + canvas.setGridMode(mode) + } label: { + Label { + Text(mode.title) + } icon: { + Image(systemName: mode.icon) + } + .font(.headline) + } + } + } label: { + Image(systemName: canvas.gridMode.icon) + .contentShape(.circle) + .frame(width: size, height: size) + .background(.regularMaterial) + .clipShape(.rect(cornerRadius: 8)) + } + .hoverEffect(.lift) + .contentTransition(.symbolEffect(.replace)) + } + func openCamera() { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { diff --git a/Memola/Features/Memos/MemosView.swift b/Memola/Features/Memos/MemosView.swift index 7e5de48..3eedd63 100644 --- a/Memola/Features/Memos/MemosView.swift +++ b/Memola/Features/Memos/MemosView.swift @@ -71,6 +71,7 @@ struct MemosView: View { let canvasObject = CanvasObject(context: managedObjectContext) canvasObject.width = 8_000 canvasObject.height = 8_000 + canvasObject.gridMode = 1 let toolObject = ToolObject(\.viewContext) toolObject.selection = 0 diff --git a/Memola/Persistence/Objects/CanvasObject.swift b/Memola/Persistence/Objects/CanvasObject.swift index 66fc646..02b973c 100644 --- a/Memola/Persistence/Objects/CanvasObject.swift +++ b/Memola/Persistence/Objects/CanvasObject.swift @@ -12,6 +12,7 @@ import Foundation final class CanvasObject: NSManagedObject { @NSManaged var width: CGFloat @NSManaged var height: CGFloat + @NSManaged var gridMode: Int16 @NSManaged var memo: MemoObject? @NSManaged var graphicContext: GraphicContextObject diff --git a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents index 3dddb53..5e6773c 100644 --- a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents +++ b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents @@ -1,6 +1,7 @@ + From f104416fe48ad1d725f726be741d2772155f27ee Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Tue, 25 Jun 2024 20:30:58 +0700 Subject: [PATCH 3/3] feat: update point texture --- .../Universal.mipmapset/Contents.json | 2 +- .../Universal.mipmapset/point-texture 1.png | Bin 0 -> 6745 bytes .../Universal.mipmapset/point-texture.png | Bin 3614 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/point-texture 1.png delete mode 100644 Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/point-texture.png diff --git a/Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/Contents.json b/Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/Contents.json index 1574a62..531f7aa 100644 --- a/Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/Contents.json +++ b/Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/Contents.json @@ -5,7 +5,7 @@ }, "levels" : [ { - "filename" : "point-texture.png", + "filename" : "point-texture 1.png", "mipmap-level" : "base" } ] diff --git a/Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/point-texture 1.png b/Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/point-texture 1.png new file mode 100644 index 0000000000000000000000000000000000000000..fbc680f9cd0fa654de5bdb3b2c992b870131d823 GIT binary patch literal 6745 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 literal 0 HcmV?d00001 diff --git a/Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/point-texture.png b/Memola/Resources/Assets/Assets.xcassets/textures/point-texture.textureset/Universal.mipmapset/point-texture.png deleted file mode 100644 index 6653eb06fcb219198ba7fb3b9186a1ec05cb0bc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3614 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 IGY+@@2X<(c4*&oF