├── .github └── workflows │ └── swift.yml ├── .gitignore ├── .periphery.yml ├── .pre-commit-config.yaml ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── WorkspaceSettings.xcsettings ├── .vscode └── settings.json ├── Demos ├── CLI │ ├── CLI.swift │ ├── Media.xcassets │ │ ├── Contents.json │ │ └── HD-Testcard-original.imageset │ │ │ ├── Contents.json │ │ │ └── HD-Testcard-original.jpg │ └── OffscreenDemo.swift ├── Demos │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── appstore1024.png │ │ │ ├── ipad152.png │ │ │ ├── ipad76.png │ │ │ ├── ipadNotification20.png │ │ │ ├── ipadNotification40.png │ │ │ ├── ipadPro167.png │ │ │ ├── ipadSettings29.png │ │ │ ├── ipadSettings58.png │ │ │ ├── ipadSpotlight40.png │ │ │ ├── ipadSpotlight80.png │ │ │ ├── iphone120.png │ │ │ ├── iphone180.png │ │ │ ├── mac1024.png │ │ │ ├── mac128.png │ │ │ ├── mac16.png │ │ │ ├── mac256.png │ │ │ ├── mac32.png │ │ │ ├── mac512.png │ │ │ ├── mac64.png │ │ │ ├── notification40.png │ │ │ ├── notification60.png │ │ │ ├── settings58.png │ │ │ ├── settings87.png │ │ │ ├── spotlight120.png │ │ │ └── spotlight80.png │ │ ├── BlueSkySkybox.textureset │ │ │ ├── Contents.json │ │ │ └── Universal.mipmapset │ │ │ │ ├── BlueSkySkybox.png │ │ │ │ └── Contents.json │ │ ├── Checkerboard.textureset │ │ │ ├── Contents.json │ │ │ └── Universal.mipmapset │ │ │ │ ├── Checkerboard.png │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── custom.camera.badge.arrow.down.rectangle.symbolset │ │ │ ├── Contents.json │ │ │ └── custom.camera.badge.arrow.down.rectangle.svg │ │ └── seamless-foods-mixed-0020.textureset │ │ │ ├── Contents.json │ │ │ └── Universal.mipmapset │ │ │ ├── Contents.json │ │ │ └── seamless-foods-mixed-0020.jpeg │ ├── CSGDemoView.swift │ ├── ContentView-VisionOS.swift │ ├── ContentView.swift │ ├── Demos.entitlements │ ├── DemosApp-VisionOS.swift │ ├── DemosApp.swift │ ├── Info-visionOS.plist │ ├── Info.plist │ ├── Interactive │ │ ├── BallRotationModifier.swift │ │ ├── FirstPersonInteractiveViewModifier.swift │ │ ├── GameControllerWidget.swift │ │ └── MovementController.swift │ ├── MetalCanvas │ │ └── MetalCanvas.swift │ ├── Output │ │ ├── perseverance_01.ktx │ │ ├── perseverance_02.ktx │ │ ├── perseverance_03.ktx │ │ ├── perseverance_04.ktx │ │ ├── perseverance_05.ktx │ │ ├── perseverance_06.ktx │ │ ├── perseverance_07.ktx │ │ ├── perseverance_08.ktx │ │ ├── perseverance_09.ktx │ │ ├── perseverance_10.ktx │ │ ├── perseverance_11.ktx │ │ ├── perseverance_12.ktx │ │ ├── perseverance_13.ktx │ │ └── perseverance_14.ktx │ ├── Particles │ │ ├── Particles.swift │ │ ├── ParticlesRenderJob.swift │ │ └── SimulationView.swift │ ├── Particles2 │ │ └── Particles2.swift │ ├── PerseveranceTiles │ │ ├── perseverance_01.ktx │ │ ├── perseverance_02.ktx │ │ ├── perseverance_03.ktx │ │ ├── perseverance_04.ktx │ │ ├── perseverance_05.ktx │ │ ├── perseverance_06.ktx │ │ ├── perseverance_07.ktx │ │ ├── perseverance_08.ktx │ │ ├── perseverance_09.ktx │ │ ├── perseverance_10.ktx │ │ ├── perseverance_11.ktx │ │ ├── perseverance_12.ktx │ │ ├── perseverance_13.ktx │ │ └── perseverance_14.ktx │ ├── PixelFormatsView.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Scratch.swift │ ├── ScreenSpaceDemoView.swift │ ├── ShaderToy │ │ ├── ShaderToy.metal │ │ └── ShaderToyView.swift │ ├── SimpleScene │ │ ├── BarramundiFish.glb │ │ ├── Cube.glb │ │ ├── FlatMaterial.swift │ │ ├── GLTF+YAMesh.swift │ │ ├── PanoramaRenderJob.swift │ │ ├── SimpleJobsBasedRenderPass.swift │ │ ├── SimpleMesh.swift │ │ ├── SimpleScene+Demo.swift │ │ ├── SimpleScene.swift │ │ ├── SimpleSceneInspector.swift │ │ ├── SimpleSceneMapView.swift │ │ ├── SimpleSceneRenderPass.swift │ │ ├── SimpleSceneView.swift │ │ ├── TextureManager.swift │ │ ├── UnlitMaterial.swift │ │ └── YAMesh+Conversion.swift │ ├── Support │ │ ├── Bindings.swift │ │ ├── Cache.swift │ │ ├── Counters.swift │ │ ├── DisplayLink.swift │ │ ├── Gradient.swift │ │ ├── Logging.swift │ │ ├── Names.swift │ │ ├── Projections.swift │ │ ├── Resources.swift │ │ ├── Support.swift │ │ ├── SwiftUI+Support.swift │ │ ├── TarArchive.swift │ │ ├── adjectives.txt │ │ └── nouns.txt │ ├── Teapot.ply │ ├── TestcardTiles │ │ ├── Testcard_01.ktx │ │ ├── Testcard_02.ktx │ │ ├── Testcard_03.ktx │ │ ├── Testcard_04.ktx │ │ ├── Testcard_05.ktx │ │ ├── Testcard_06.ktx │ │ ├── Testcard_07.ktx │ │ ├── Testcard_08.ktx │ │ ├── Testcard_09.ktx │ │ ├── Testcard_10.ktx │ │ ├── Testcard_11.ktx │ │ └── Testcard_12.ktx │ ├── TextureView.swift │ └── Volumetric │ │ ├── StanfordVolumeData.tar │ │ ├── VolumeData.swift │ │ ├── VolumetricRenderPass.swift │ │ └── VolumetricView.swift ├── Packages │ ├── Compute │ │ ├── .gitignore │ │ ├── .swiftpm │ │ │ └── xcode │ │ │ │ └── package.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ ├── Package.swift │ │ └── Sources │ │ │ ├── Compute │ │ │ ├── Compute.swift │ │ │ └── ShaderFunction.swift │ │ │ └── ComputeTool │ │ │ ├── BitonicSort.metal │ │ │ ├── BitonicSort.swift │ │ │ ├── ComputeTool.swift │ │ │ ├── GameOfLife.metal │ │ │ ├── GameOfLife.swift │ │ │ ├── RandomFill.metal │ │ │ ├── RandomFill.swift │ │ │ └── Support.swift │ └── DemosSupport │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── Sources │ │ └── DemosSupport │ │ │ └── SpatialLookupTable.swift │ │ └── Tests │ │ └── DemosSupportTests │ │ └── DemosSupportTests.swift ├── RenderKitDemos.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── Demos.xcscheme └── xcconfig │ ├── CLI-Debug.xcconfig │ ├── CLI-Release.xcconfig │ ├── CLI-Shared.xcconfig │ ├── Demos-Debug.xcconfig │ ├── Demos-Release.xcconfig │ ├── Demos-Shared.xcconfig │ ├── Project-Debug.xcconfig │ ├── Project-Release.xcconfig │ └── Project-Shared.xcconfig ├── Documentation ├── Exported PNG image.png ├── Mesh.plantuml ├── Meshes.svg ├── Screenshot 2023-07-01 at 08.06.29.png └── Screenshot 2023-09-19 at 19.51.00.png ├── LICENSE ├── MyPlayground.playground ├── Contents.swift └── contents.xcplayground ├── Package.swift ├── README.md ├── Sources ├── RenderKit │ ├── MetalView.swift │ ├── OffscreenRenderPass.swift │ ├── PackedFloat.swift │ ├── RenderKitShaders.swift │ ├── RenderPass.swift │ ├── RendererView.swift │ ├── SimpleVertex.swift │ ├── Support │ │ ├── Functions.swift │ │ ├── LOLID2.swift │ │ ├── Logging.swift │ │ ├── Metal+Support.swift │ │ ├── Scratch.swift │ │ ├── Shape3D.swift │ │ └── Support.swift │ ├── VertexDescriptor.swift │ ├── VisionOS │ │ ├── Assets.xcassets │ │ │ ├── ColorMap.textureset │ │ │ │ ├── Contents.json │ │ │ │ └── Universal.mipmapset │ │ │ │ │ ├── ColorMap.png │ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ └── Renderer.swift │ └── YAMesh │ │ └── YAMesh.swift ├── RenderKitScratch │ ├── CSG.swift │ ├── CircularPairs.swift │ ├── HighContrastColors.swift │ ├── PLYEncoder.swift │ ├── Scratch.swift │ ├── Shapes+CSG.swift │ ├── Shapes+Intersections.swift │ ├── Shapes+ShapeScript.swift │ ├── Shapes.swift │ └── WingedEdge.swift └── RenderKitShaders │ ├── Classic │ ├── BlinnPhongShaders.metal │ ├── CheckerBoardCompute.metal │ ├── GPULifeKernel.metal │ ├── ParticleShaders.metal │ ├── ShaderToy.metal │ ├── Support.metal │ ├── Voxels.metal │ └── include │ │ ├── Bindings.h │ │ ├── BlinnPhongShaders.h │ │ ├── Common.h │ │ ├── GLSLCompat.h │ │ ├── ParticleShaders.h │ │ ├── Random.h │ │ ├── Shaders.h │ │ ├── Support.h │ │ └── Voxels.h │ ├── Empty.c │ ├── FlatShader.metal │ ├── GridShader.metal │ ├── MarchingCubes.metal │ ├── Noise.metal │ ├── PanoramicShaders.metal │ ├── ParticlesShader.metal │ ├── ReallyFlatShader.metal │ ├── Scratch │ ├── DemoBlit.metal │ └── MeshShaders.metal │ ├── UnlitShader.metal │ ├── VolumeShaders.metal │ ├── immersiveShaders.metal │ ├── include │ ├── CommonTypes.h │ ├── FlatShader.h │ ├── GLSLCompat.h │ ├── GraphToy.h │ ├── ImmersiveShaders.h │ ├── MetalSupport.h │ ├── PanoramicShaders.h │ ├── Random.h │ ├── RenderKitShaders.h │ ├── UnlitShader.h │ └── VolumeShaders.h │ └── voronoiNoise.metal ├── TODO.md └── Tests └── RenderKitTests ├── CSGTests.swift ├── RenderKit3Tests.swift ├── ScratchTests.swift ├── VertexDescriptorTests.swift └── WingedEdgeTests.swift /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | env: 3 | XCODE_SCHEME: "RenderKitDemos" 4 | XCODE_PROJECT_PATH: "./Demos" 5 | XCODE_VERSION: "latest-stable" 6 | on: 7 | push: 8 | pull_request: 9 | jobs: 10 | swift-build: 11 | runs-on: macos-15 # macos-latest 12 | steps: 13 | - uses: maxim-lobanov/setup-xcode@v1 14 | with: 15 | xcode-version: ${{ env.XCODE_VERSION }} 16 | - uses: actions/checkout@v3 17 | - run: swift build -v 18 | - run: swift test -v 19 | xcode-build: 20 | strategy: 21 | matrix: 22 | destination: ["platform=iOS Simulator,name=iPhone 16 Plus", "generic/platform=iOS", "generic/platform=macOS"] 23 | runs-on: macos-15 # macos-latest 24 | steps: 25 | - uses: maxim-lobanov/setup-xcode@v1 26 | with: 27 | xcode-version: ${{ env.XCODE_VERSION }} 28 | - uses: actions/checkout@v3 29 | - run: xcodebuild -scheme "${{ env.XCODE_SCHEME }}" -destination "${{ matrix.destination }}" build CODE_SIGNING_ALLOWED=NO 30 | working-directory: "${{ env.XCODE_PROJECT_PATH }}" 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | Package.resolved 10 | -------------------------------------------------------------------------------- /.periphery.yml: -------------------------------------------------------------------------------- 1 | retain_public: true 2 | targets: 3 | - RenderKit 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: swiftlint 5 | name: swiftlint lint 6 | entry: /opt/homebrew/bin/swiftlint lint --quiet 7 | language: system 8 | types: [swift] 9 | - id: swiftlint-fix 10 | name: swiftlint lint --fix 11 | entry: /opt/homebrew/bin/swiftlint lint --quiet --fix 12 | language: system 13 | types: [swift] 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v2.3.0 16 | hooks: 17 | - id: check-case-conflict 18 | - id: check-executables-have-shebangs 19 | - id: check-json 20 | - id: check-merge-conflict 21 | - id: check-symlinks 22 | - id: check-toml 23 | - id: check-xml 24 | - id: check-yaml 25 | - id: end-of-file-fixer 26 | - id: trailing-whitespace 27 | # - id: check-shebang-scripts-are-executable 28 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.7 2 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --disable andOperator 2 | --disable emptyBraces 3 | --disable fileHeader 4 | --disable redundantParens 5 | --disable trailingClosures 6 | --enable isEmpty 7 | 8 | --elseposition next-line 9 | --ifdef indent 10 | --patternlet inline 11 | --stripunusedargs closure-only 12 | --closingparen balanced 13 | --wraparguments preserve 14 | --wrapcollections before-first 15 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /Demos/CLI/CLI.swift: -------------------------------------------------------------------------------- 1 | import RenderKit 2 | import Foundation 3 | import Metal 4 | import MetalKit 5 | import ModelIO 6 | 7 | @main 8 | struct Main { 9 | static func main() async throws { 10 | // try await OffscreenDemo.main() 11 | 12 | let device = MTLCreateSystemDefaultDevice()! 13 | 14 | let mesh = try Sphere().toYAMesh(allocator: MTKMeshBufferAllocator(device: device), device: device) 15 | print(mesh) 16 | } 17 | } 18 | 19 | extension YAMesh { 20 | func toMDLMesh() throws -> MDLMesh { 21 | let vertexBuffers = vertexBufferViews.map { bufferView in 22 | return MDLMeshBufferData(type: .vertex, data: Data(bufferView)) 23 | } 24 | let vertexDescriptor = MDLVertexDescriptor(vertexDescriptor) 25 | let submeshes = submeshes.map { submesh in 26 | let indexBuffer = MDLMeshBufferData(type: .index, data: Data(submesh.indexBufferView)) 27 | return MDLSubmesh(indexBuffer: indexBuffer, indexCount: 0, indexType: .invalid, geometryType: .lines, material: nil) 28 | } 29 | return MDLMesh(vertexBuffers: vertexBuffers, vertexCount: 0, descriptor: vertexDescriptor, submeshes: submeshes) 30 | } 31 | } 32 | 33 | extension YAMesh { 34 | func write(to url: URL) throws { 35 | let mdlMesh = try toMDLMesh() 36 | let asset = MDLAsset() 37 | asset.add(mdlMesh) 38 | try asset.export(to: url) 39 | } 40 | } 41 | 42 | extension Data { 43 | init(_ buffer: BufferView) { 44 | fatalError() 45 | } 46 | } 47 | 48 | extension MDLVertexDescriptor { 49 | convenience init(_ vertexDescriptor: VertexDescriptor) { 50 | fatalError() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Demos/CLI/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demos/CLI/Media.xcassets/HD-Testcard-original.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "HD-Testcard-original.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demos/CLI/Media.xcassets/HD-Testcard-original.imageset/HD-Testcard-original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/CLI/Media.xcassets/HD-Testcard-original.imageset/HD-Testcard-original.jpg -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/appstore1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/appstore1024.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipad152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipad152.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipad76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipad76.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadNotification20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadNotification20.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadNotification40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadNotification40.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadPro167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadPro167.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadSettings29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadSettings29.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadSettings58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadSettings58.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadSpotlight40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadSpotlight40.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadSpotlight80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/ipadSpotlight80.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/iphone120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/iphone120.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/iphone180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/iphone180.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac1024.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac128.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac16.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac256.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac32.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac512.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/mac64.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/notification40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/notification40.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/notification60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/notification60.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/settings58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/settings58.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/settings87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/settings87.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/spotlight120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/spotlight120.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/AppIcon.appiconset/spotlight80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/AppIcon.appiconset/spotlight80.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/BlueSkySkybox.textureset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "textures" : [ 7 | { 8 | "filename" : "Universal.mipmapset", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/BlueSkySkybox.textureset/Universal.mipmapset/BlueSkySkybox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/BlueSkySkybox.textureset/Universal.mipmapset/BlueSkySkybox.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/BlueSkySkybox.textureset/Universal.mipmapset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "levels" : [ 7 | { 8 | "filename" : "BlueSkySkybox.png", 9 | "mipmap-level" : "base" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/Checkerboard.textureset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "textures" : [ 7 | { 8 | "filename" : "Universal.mipmapset", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/Checkerboard.textureset/Universal.mipmapset/Checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/Checkerboard.textureset/Universal.mipmapset/Checkerboard.png -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/Checkerboard.textureset/Universal.mipmapset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "levels" : [ 7 | { 8 | "filename" : "Checkerboard.png", 9 | "mipmap-level" : "base" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/custom.camera.badge.arrow.down.rectangle.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "custom.camera.badge.arrow.down.rectangle.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/seamless-foods-mixed-0020.textureset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "textures" : [ 7 | { 8 | "filename" : "Universal.mipmapset", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/seamless-foods-mixed-0020.textureset/Universal.mipmapset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "levels" : [ 7 | { 8 | "filename" : "seamless-foods-mixed-0020.jpeg", 9 | "mipmap-level" : "base" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Demos/Demos/Assets.xcassets/seamless-foods-mixed-0020.textureset/Universal.mipmapset/seamless-foods-mixed-0020.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Assets.xcassets/seamless-foods-mixed-0020.textureset/Universal.mipmapset/seamless-foods-mixed-0020.jpeg -------------------------------------------------------------------------------- /Demos/Demos/ContentView-VisionOS.swift: -------------------------------------------------------------------------------- 1 | #if os(visionOS) 2 | import SwiftUI 3 | import RenderKit 4 | 5 | struct ContentView: View { 6 | @State private var showImmersiveSpace = false 7 | @State private var immersiveSpaceIsShown = false 8 | 9 | @Environment(\.openImmersiveSpace) var openImmersiveSpace 10 | @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace 11 | 12 | var body: some View { 13 | VStack { 14 | VStack { 15 | Toggle("Show/Hide Immersive Space", isOn: $showImmersiveSpace) 16 | .toggleStyle(.button) 17 | }.padding().glassBackgroundEffect() 18 | } 19 | .onChange(of: showImmersiveSpace) { _, newValue in 20 | Task { 21 | if newValue { 22 | switch await openImmersiveSpace(id: "ImmersiveSpace") { 23 | case .opened: 24 | immersiveSpaceIsShown = true 25 | case .error, .userCancelled: 26 | fallthrough 27 | @unknown default: 28 | immersiveSpaceIsShown = false 29 | showImmersiveSpace = false 30 | } 31 | } else if immersiveSpaceIsShown { 32 | await dismissImmersiveSpace() 33 | immersiveSpaceIsShown = false 34 | } 35 | } 36 | } 37 | } 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /Demos/Demos/ContentView.swift: -------------------------------------------------------------------------------- 1 | #if !os(visionOS) 2 | import SwiftUI 3 | import RenderKit 4 | import Everything 5 | 6 | struct ContentView: View { 7 | enum Demo: String, CaseIterable, Hashable { 8 | case simpleScene = "Simple Scene" 9 | case simpleSceneExtended = "Simple Scene (Extended)" 10 | case volumetric = "Volumetric" 11 | // case gltf = "GLTF" 12 | case simulationSUI 13 | // case screenSpaceDemoView = "ScreenSpaceDemoView" 14 | case particles2 15 | case pixelFormats 16 | case csgDemoView 17 | } 18 | 19 | @State 20 | var demo: Demo = .simpleScene 21 | 22 | var body: some View { 23 | NavigationStack { 24 | Group { 25 | List { 26 | ForEach(Demo.allCases, id: \.self) { demo in 27 | NavigationLink(value: demo) { 28 | Text(verbatim: demo.rawValue) 29 | } 30 | } 31 | } 32 | #if os(macOS) 33 | .frame(maxWidth: 320) 34 | .cornerRadius(8) 35 | .padding() 36 | .frame(maxWidth: .infinity) 37 | .background(.ultraThickMaterial) 38 | #endif 39 | } 40 | .navigationTitle("RenderKit") 41 | .navigationDestination(for: Demo.self) { demo in 42 | switch demo { 43 | case .simpleScene: 44 | let device = MTLCreateSystemDefaultDevice()! 45 | let scene = try! SimpleScene.demo(device: device) 46 | CoreSimpleSceneView(scene: .constant(scene)) 47 | .navigationTitle(demo.rawValue) 48 | case .simpleSceneExtended: 49 | SimpleSceneView() 50 | .navigationTitle(demo.rawValue) 51 | case .volumetric: 52 | VolumetricView() 53 | .navigationTitle(demo.rawValue) 54 | // case .gltf: 55 | // GLTFView() 56 | // .navigationTitle(demo.rawValue) 57 | case .simulationSUI: 58 | SimulationView() 59 | // case .screenSpaceDemoView: 60 | // ScreenSpaceDemoView() 61 | case .particles2: 62 | Particles2View() 63 | case .pixelFormats: 64 | PixelFormatsView() 65 | case .csgDemoView: 66 | CSGDemoView() 67 | } 68 | } 69 | } 70 | .metalDevice(MTLCreateSystemDefaultDevice()!) 71 | } 72 | } 73 | 74 | #Preview { 75 | ContentView() 76 | } 77 | #endif 78 | -------------------------------------------------------------------------------- /Demos/Demos/Demos.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.bluetooth 8 | 9 | com.apple.security.device.usb 10 | 11 | com.apple.security.files.user-selected.read-write 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Demos/Demos/DemosApp-VisionOS.swift: -------------------------------------------------------------------------------- 1 | #if os(visionOS) 2 | import SwiftUI 3 | import CompositorServices 4 | import RenderKit 5 | 6 | /* 7 | 8 | 9 | 10 | 11 | UISceneConfigurations 12 | 13 | UIApplicationSupportsMultipleScenes 14 | 15 | UIApplicationPreferredDefaultSceneSessionRole 16 | UIWindowSceneSessionRoleVolumetricApplication 17 | 18 | 19 | */ 20 | 21 | @main 22 | struct DemosApp: App { 23 | var body: some Scene { 24 | WindowGroup { 25 | ContentView() 26 | }.windowStyle(.volumetric) 27 | ImmersiveSpace(id: "ImmersiveSpace") { 28 | CompositorLayer(configuration: ContentStageConfiguration()) { layerRenderer in 29 | try! Renderer(layerRenderer).startRenderLoop() 30 | } 31 | } 32 | .immersionStyle(selection: .constant(.full), in: .full) 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Demos/Demos/Info-visionOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UISceneConfigurations 8 | 9 | UIApplicationSupportsMultipleScenes 10 | 11 | UIApplicationPreferredDefaultSceneSessionRole 12 | UIWindowSceneSessionRoleVolumetricApplication 13 | 14 | GCSupportedGameControllers 15 | 16 | 17 | ProfileName 18 | ExtendedGamepad 19 | 20 | 21 | ProfileName 22 | MicroGamepad 23 | 24 | 25 | ProfileName 26 | DirectionalGamepad 27 | 28 | 29 | GCSupportsControllerUserInteraction 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Demos/Demos/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GCSupportedGameControllers 6 | 7 | 8 | ProfileName 9 | ExtendedGamepad 10 | 11 | 12 | ProfileName 13 | MicroGamepad 14 | 15 | 16 | ProfileName 17 | DirectionalGamepad 18 | 19 | 20 | GCSupportsControllerUserInteraction 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Demos/Demos/Interactive/FirstPersonInteractiveViewModifier.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SIMDSupport 3 | import GameController 4 | import simd 5 | 6 | public struct FirstPersonInteractiveViewModifier: ViewModifier, @unchecked Sendable { 7 | @Environment(\.displayLink) 8 | var displayLink 9 | 10 | @State 11 | var movementController: MovementController? 12 | 13 | @FocusState 14 | var renderViewFocused: Bool 15 | 16 | @State 17 | var movementConsumerTask: Task<(), Never>? 18 | 19 | @Binding 20 | var camera: Camera 21 | 22 | public func body(content: Content) -> some View { 23 | content 24 | .task() { 25 | guard let displayLink else { 26 | return 27 | } 28 | let movementController = MovementController(displayLink: displayLink) 29 | movementController.focused = renderViewFocused 30 | self.movementController = movementController 31 | #if os(macOS) 32 | movementController.disableUIKeys() 33 | #endif 34 | movementConsumerTask = Task.detached { [movementController] in 35 | for await event in movementController.events() { 36 | Counters.shared.increment(counter: "Consumption") 37 | switch event.payload { 38 | case .movement(let movement): 39 | let target = camera.target 40 | let angle = atan2(target.z, target.x) - .pi / 2 41 | let rotation = simd_quaternion(angle, [0, -1, 0]) 42 | Task { 43 | await MainActor.run { 44 | camera.transform.translation += simd_act(rotation, movement * 0.1) 45 | } 46 | } 47 | case .rotation(let rotation): 48 | Task { 49 | await MainActor.run { 50 | camera.heading.degrees += Double(rotation * 2) 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | .onChange(of: renderViewFocused) { 58 | movementController?.focused = renderViewFocused 59 | } 60 | .onDisappear { 61 | movementConsumerTask?.cancel() 62 | movementConsumerTask = nil 63 | } 64 | .focusable(interactions: .automatic) 65 | .focused($renderViewFocused) 66 | .focusEffectDisabled() 67 | .defaultFocus($renderViewFocused, true) 68 | .onKeyPress(.escape, action: { 69 | renderViewFocused = false 70 | return .handled 71 | }) 72 | .overlay(alignment: .topTrailing) { 73 | Group { 74 | Group { 75 | if renderViewFocused { 76 | //Image(systemName: "dot.square.fill").foregroundStyle(.selection) 77 | Text("Focused") 78 | } 79 | else { 80 | Text("Unfocused") 81 | //Image(systemName: "dot.square").foregroundStyle(.selection) 82 | } 83 | } 84 | .padding() 85 | .background(.regularMaterial) 86 | } 87 | .padding() 88 | } 89 | .overlay(alignment: .bottomLeading) { 90 | GameControllerWidget() 91 | .padding() 92 | } 93 | .overlay(alignment: .bottomLeading) { 94 | GameControllerWidget() 95 | .padding() 96 | } 97 | } 98 | } 99 | 100 | public extension View { 101 | func firstPersonInteractive(camera: Binding) -> some View { 102 | modifier(FirstPersonInteractiveViewModifier(camera: camera)) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Demos/Demos/MetalCanvas/MetalCanvas.swift: -------------------------------------------------------------------------------- 1 | //import CoreGraphics 2 | //import RenderKit 3 | //import Metal 4 | //import SwiftUI 5 | // 6 | //struct MetalGraphicsContext { 7 | // enum Command { 8 | // case stroke(Path) 9 | // } 10 | // 11 | // var commands: [Command] = [] 12 | // 13 | // mutating func stroke(path: Path) { 14 | // commands.append(.stroke(path)) 15 | // } 16 | //} 17 | // 18 | //struct MetalGraphicsCanvas { 19 | // typealias Configuration = OffscreenRenderPassConfiguration 20 | // 21 | // var size: CGSize 22 | // 23 | // var renderPass: any RenderPass 24 | // var configuration: Configuration 25 | // 26 | // init(size: CGSize) throws { 27 | // self.size = size 28 | // let device = MTLCreateSystemDefaultDevice()! 29 | // configuration = OffscreenRenderPassConfiguration(device: device, size: size) 30 | // configuration.colorPixelFormat = .bgra10_xr_srgb 31 | // configuration.update() 32 | // renderPass = AnyRenderPass { _, _ in 33 | // fatalError() 34 | // } 35 | // drawableSizeWillChange: { _, _, _ in 36 | // fatalError() 37 | // } 38 | // draw: { _, _, _, _, _ in 39 | // fatalError() 40 | // } 41 | // } 42 | // 43 | // func draw(_ block: (inout MetalGraphicsContext) throws -> R) rethrows -> R { 44 | // var context = MetalGraphicsContext() 45 | // let result = try block(&context) 46 | // try! render(context.commands) // TODO: try! 47 | // return result 48 | // } 49 | // 50 | // func render(_ context: [MetalGraphicsContext.Command]) throws { 51 | // fatalError() 52 | // } 53 | //} 54 | -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_01.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_01.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_02.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_02.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_03.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_03.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_04.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_04.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_05.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_05.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_06.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_06.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_07.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_07.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_08.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_08.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_09.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_09.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_10.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_10.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_11.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_11.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_12.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_12.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_13.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_13.ktx -------------------------------------------------------------------------------- /Demos/Demos/Output/perseverance_14.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Output/perseverance_14.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_01.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_01.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_02.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_02.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_03.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_03.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_04.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_04.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_05.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_05.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_06.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_06.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_07.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_07.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_08.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_08.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_09.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_09.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_10.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_10.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_11.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_11.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_12.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_12.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_13.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_13.ktx -------------------------------------------------------------------------------- /Demos/Demos/PerseveranceTiles/perseverance_14.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/PerseveranceTiles/perseverance_14.ktx -------------------------------------------------------------------------------- /Demos/Demos/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demos/Demos/Scratch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scratch.swift 3 | // Demos 4 | // 5 | // Created by Jonathan Wight on 9/17/23. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | 11 | struct VolumeData { 12 | var directoryURL: URL 13 | var size: MTLSize 14 | 15 | func load() throws -> (MTLDevice) throws -> MTLTexture { 16 | let slices = try FileManager().contentsOfDirectory(atPath: directoryURL.path) 17 | .map { directoryURL.appendingPathComponent($0) } 18 | .sorted { lhs, rhs in 19 | let lhs = Int(lhs.pathExtension)! 20 | let rhs = Int(rhs.pathExtension)! 21 | return lhs < rhs 22 | } 23 | .map { 24 | let data = try Data(contentsOf: $0) 25 | return data.withUnsafeBytes { buffer in 26 | buffer.bindMemory(to: UInt16.self).map { 27 | UInt16(bigEndian: $0) 28 | } 29 | } 30 | } 31 | 32 | return { device in 33 | let textureDescriptor = MTLTextureDescriptor() 34 | textureDescriptor.textureType = .type3D 35 | textureDescriptor.pixelFormat = .r16Uint 36 | textureDescriptor.width = size.width 37 | textureDescriptor.height = size.height 38 | textureDescriptor.depth = size.depth 39 | 40 | guard let texture = device.makeTexture(descriptor: textureDescriptor) else { 41 | fatalError() 42 | } 43 | texture.label = directoryURL.lastPathComponent 44 | 45 | for slice in slices { 46 | let region = MTLRegionMake3D(0, 0, 0, size.width, size.height, 1) 47 | texture.replace(region: region, mipmapLevel: 0, slice: 0, withBytes: slice, bytesPerRow: size.width * 2, bytesPerImage: size.width * size.height * 2) 48 | } 49 | 50 | return texture 51 | } 52 | } 53 | } 54 | 55 | //Description: CT study of a cadaver head 56 | //Dimensions: 113 slices of 256 x 256 pixels, 57 | // voxel grid is rectangular, and 58 | // X:Y:Z shape of each voxel is 1:1:2 59 | //Files: 113 binary files, one file per slice 60 | //File format: 16-bit integers (Mac byte ordering), file contains no header 61 | //Data source: acquired on a General Electric CT Scanner and provided 62 | // courtesy of North Carolina Memorial Hospital 63 | -------------------------------------------------------------------------------- /Demos/Demos/ShaderToy/ShaderToy.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct ShaderToyVertex { 7 | vector_float3 position [[attribute(0)]]; 8 | vector_float3 normal [[attribute(1)]]; 9 | vector_float2 textureCoords [[attribute(2)]]; 10 | }; 11 | 12 | struct ShaderToyVertexUniforms { 13 | simd_float3x4 transform; 14 | }; 15 | 16 | struct ShaderToyFragmentUniforms { 17 | float time; 18 | vector_float2 scale; 19 | }; 20 | 21 | struct Fragment { 22 | vector_float4 position [[position]]; 23 | }; 24 | 25 | // MARK: - 26 | 27 | enum ShaderToyID { 28 | kShaderToyID_Vertices = 0, 29 | kShaderToyID_VertexShaderUniforms = 1, 30 | kShaderToyID_FragmentShaderUniforms = 2, 31 | kShaderToyID_PixelateFunctionConstant = 0, 32 | }; 33 | 34 | // MARK: - 35 | 36 | constant bool pixelate [[function_constant(kShaderToyID_PixelateFunctionConstant)]]; 37 | 38 | [[vertex]] 39 | Fragment shaderToyVertexShader(ShaderToyVertex vertexIn [[stage_in]], constant ShaderToyVertexUniforms &uniforms [[buffer(kShaderToyID_VertexShaderUniforms)]]) 40 | { 41 | return { 42 | .position = vector_float4(vector_float4(vertexIn.position, 1.0) * uniforms.transform, 1.0) 43 | }; 44 | } 45 | 46 | [[fragment]] 47 | vector_float4 shaderToyFragmentShader(Fragment fragmentIn [[stage_in]], constant ShaderToyFragmentUniforms &uniforms [[buffer(kShaderToyID_FragmentShaderUniforms)]]) 48 | { 49 | vector_float2 vert = fragmentIn.position.xy * uniforms.scale; 50 | if (pixelate) { 51 | vert = floor(vert); 52 | } 53 | const auto step = vector_float2(sin(uniforms.time), cos(uniforms.time)) * 20.0; 54 | const auto scale = ((sin(uniforms.time / 60.0) + 1.0) / 5.0) + 0.2; 55 | const auto r = sin((vert.x + step.x) * scale) + cos((vert.y + step.x) * scale); 56 | const auto g = sin((vert.x + step.x) * scale) + cos((vert.y + step.y) * scale); 57 | const auto b = sin((vert.x + step.y) * scale) + cos((vert.y + step.y) * scale); 58 | return vector_float4(r, g, b, 1.0); 59 | } 60 | -------------------------------------------------------------------------------- /Demos/Demos/SimpleScene/BarramundiFish.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/SimpleScene/BarramundiFish.glb -------------------------------------------------------------------------------- /Demos/Demos/SimpleScene/Cube.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/SimpleScene/Cube.glb -------------------------------------------------------------------------------- /Demos/Demos/SimpleScene/PanoramaRenderJob.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import ModelIO 3 | import Metal 4 | import MetalKit 5 | import SIMDSupport 6 | import RenderKitShaders 7 | import RenderKit 8 | import Observation 9 | import Everything 10 | 11 | class PanoramaRenderJob: SceneRenderJob { 12 | var renderPipelineState: MTLRenderPipelineState? 13 | var depthStencilState: MTLDepthStencilState? 14 | var mesh: YAMesh? 15 | 16 | var scene: SimpleScene 17 | var panorama: Panorama 18 | var textureManager: TextureManager 19 | var textures: [MTLTexture] = [] 20 | 21 | init(scene: SimpleScene, textureManager: TextureManager, panorama: Panorama) { 22 | self.scene = scene 23 | self.textureManager = textureManager 24 | self.panorama = panorama 25 | } 26 | 27 | func setup(device: MTLDevice, configuration: inout Configuration) throws { 28 | let library = try! device.makeDefaultLibrary(bundle: .shadersBundle) 29 | let vertexFunction = library.makeFunction(name: "panoramicVertexShader")! 30 | let constantValues = MTLFunctionConstantValues() 31 | let fragmentFunction = try library.makeFunction(name: "panoramicFragmentShader", constantValues: constantValues) 32 | depthStencilState = device.makeDepthStencilState(descriptor: MTLDepthStencilDescriptor.always()) 33 | let renderPipelineDescriptor = MTLRenderPipelineDescriptor() 34 | renderPipelineDescriptor.vertexFunction = vertexFunction 35 | renderPipelineDescriptor.fragmentFunction = fragmentFunction 36 | renderPipelineDescriptor.colorAttachments[0].pixelFormat = configuration.colorPixelFormat 37 | renderPipelineDescriptor.depthAttachmentPixelFormat = configuration.depthStencilPixelFormat 38 | let descriptor = VertexDescriptor.packed(semantics: [.position, .normal, .textureCoordinate]) 39 | renderPipelineDescriptor.vertexDescriptor = MTLVertexDescriptor(descriptor) 40 | renderPipelineState = try device.makeRenderPipelineState(descriptor: renderPipelineDescriptor) 41 | mesh = try! panorama.mesh(device) 42 | let loader = MTKTextureLoader(device: device) 43 | textures = try panorama.tileTextures.map { try $0(loader) } 44 | } 45 | 46 | func encode(on encoder: MTLRenderCommandEncoder, size: CGSize) throws { 47 | guard let renderPipelineState, let depthStencilState else { 48 | return 49 | } 50 | encoder.withDebugGroup("Panorama") { 51 | guard let mesh else { 52 | fatalError() 53 | } 54 | encoder.setRenderPipelineState(renderPipelineState) 55 | encoder.setDepthStencilState(depthStencilState) 56 | encoder.setVertexBuffers(mesh) 57 | let cameraUniforms = CameraUniforms(projectionMatrix: scene.camera.projection.matrix(viewSize: SIMD2(size))) 58 | let inverseCameraMatrix = scene.camera.transform.matrix.inverse 59 | encoder.setVertexBytes(of: cameraUniforms, index: 1) 60 | let modelViewMatrix = inverseCameraMatrix * float4x4.translation(scene.camera.transform.translation) 61 | encoder.setVertexBytes(of: modelViewMatrix, index: 2) 62 | let uniforms = PanoramaFragmentUniforms(gridSize: panorama.tilesSize, colorFactor: [1, 1, 1, 1]) 63 | encoder.setFragmentBytes(of: uniforms, index: 0) 64 | encoder.setFragmentTextures(textures, range: 0..(device: MTLDevice, configuration: inout Configuration) throws { 12 | try jobs.forEach { job in 13 | try job.setup(device: device, configuration: &configuration) 14 | } 15 | } 16 | 17 | public func drawableSizeWillChange(device: MTLDevice, size: CGSize) throws { 18 | try jobs.forEach { job in 19 | try job.drawableSizeWillChange(device: device, size: size) 20 | } 21 | } 22 | 23 | public func draw(device: MTLDevice, size: CGSize, renderPassDescriptor: MTLRenderPassDescriptor, commandBuffer: MTLCommandBuffer) throws { 24 | try commandBuffer.withRenderCommandEncoder(descriptor: renderPassDescriptor) { encoder in 25 | encoder.label = "SimpleJobsBasedRenderPass-RenderCommandEncoder" 26 | try jobs.forEach { job in 27 | try job.encode(on: encoder, size: size) 28 | } 29 | } 30 | } 31 | } 32 | 33 | extension SimpleJobsBasedRenderPass: Observable { 34 | } 35 | -------------------------------------------------------------------------------- /Demos/Demos/SimpleScene/SimpleMesh.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import ModelIO 3 | import Metal 4 | import MetalKit 5 | import SIMDSupport 6 | import RenderKitShaders 7 | import Everything 8 | import MetalSupport 9 | import os 10 | import RenderKit 11 | import Shapes2D 12 | 13 | protocol MTLBufferProviding { 14 | var buffer: MTLBuffer { get } 15 | } 16 | 17 | extension YAMesh { 18 | static func triangle(label: String? = nil, triangle: Triangle, transform: simd_float3x2 = simd_float3x2([1, 0], [0, 1], [0, 0]), device: MTLDevice, textureCoordinate: (CGPoint) -> SIMD2) throws -> YAMesh { 19 | let vertices = [ 20 | triangle.vertices.0, 21 | triangle.vertices.1, 22 | triangle.vertices.2, 23 | ] 24 | .map { 25 | // TODO; Normal not impacted by transform. It should be. 26 | SimpleVertex(position: SIMD2($0) * transform, normal: [0, 0, 1], textureCoordinate: textureCoordinate($0)) 27 | } 28 | return try YAMesh.simpleMesh(label: label, indices: [0, 1, 2], vertices: vertices, device: device) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Demos/Demos/SimpleScene/SimpleScene+Demo.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | import RenderKit 3 | import MetalKit 4 | import Algorithms 5 | 6 | public extension SimpleScene { 7 | static func demo(device: MTLDevice) throws -> SimpleScene { 8 | let allocator = MTKMeshBufferAllocator(device: device) 9 | let cone = try Cone(extent: [0.5, 1, 0.5], segments: [20, 10]).toYAMesh(allocator: allocator, device: device) 10 | let sphere = try Sphere(extent: [0.5, 0.5, 0.5], segments: [20, 10]).toYAMesh(allocator: allocator, device: device) 11 | let capsule = try Capsule(extent: [0.25, 1, 0.25], cylinderSegments: [30, 10], hemisphereSegments: 5).toYAMesh(allocator: allocator, device: device) 12 | 13 | let meshes = [cone, sphere, capsule] 14 | 15 | let xRange = [Float](stride(from: -2, through: 2, by: 1)) 16 | let zRange = [Float](stride(from: 0, through: -10, by: -1)) 17 | 18 | let tilesSize: SIMD2 19 | let tileTextures: [(MTKTextureLoader) throws -> MTLTexture] 20 | if false { 21 | tilesSize = [6, 2] 22 | tileTextures = (1 ... 12).map { index in 23 | BundleResourceReference(bundle: .main, name: "perseverance_\(index.formatted(.number.precision(.integerLength(2))))", extension: "ktx") 24 | //ResourceReference.bundle(.main, name: "Testcard_\(index.formatted(.number.precision(.integerLength(2))))", extension: "ktx") 25 | } 26 | .map { resource -> ((MTKTextureLoader) throws -> MTLTexture) in 27 | return { loader in 28 | try loader.newTexture(resource: resource, options: [.textureStorageMode: MTLStorageMode.private.rawValue]) 29 | } 30 | } 31 | } 32 | else { 33 | tilesSize = [1, 1] 34 | tileTextures = [ { loader in 35 | try loader.newTexture(name: "BlueSkySkybox", scaleFactor: 1, bundle: .main, options: [ 36 | .textureStorageMode: MTLStorageMode.private.rawValue, 37 | .SRGB: true, 38 | ]) 39 | } 40 | ] 41 | } 42 | 43 | var models: [Model] = [] 44 | models += product(xRange, zRange).map { x, z in 45 | let hsv: SIMD3 = [Float.random(in: 0...1), 1, 1] 46 | let rgba = SIMD4(hsv.hsv2rgb(), 1.0) 47 | let material = FlatMaterial(baseColorFactor: rgba, baseColorTexture: .init(resource: BundleResourceReference(bundle: .main, name: "Checkerboard"))) 48 | return Model(transform: .translation([x, 0, z]), material: material, mesh: meshes.randomElement()!) 49 | } 50 | 51 | let fishModel = Model( 52 | transform: .translation([0, 1, 0]).rotated(angle: .degrees(90), axis: [0, 1, 0]), 53 | material: UnlitMaterial(baseColorFactor: [1, 0, 1, 1], baseColorTexture: .init(resource: BundleResourceReference(bundle: .main, name: "seamless-foods-mixed-0020"))), 54 | mesh: try YAMesh(gltf: "BarramundiFish", device: device) 55 | ) 56 | models.append(fishModel) 57 | 58 | let panorama = Panorama(tilesSize: tilesSize, tileTextures: tileTextures) { device in 59 | try Sphere(extent: [95, 95, 95], inwardNormals: true).toYAMesh(allocator: MTKMeshBufferAllocator(device: device), device: device) 60 | } 61 | 62 | let scene = SimpleScene( 63 | camera: Camera(transform: .translation([0, 0, 2]), target: [0, 0, -1], projection: .perspective(.init(fovy: .degrees(90), zClip: 0.1 ... 100))), 64 | light: .init(position: .translation([-2, 2, -1]), color: [1, 1, 1], power: 1), 65 | ambientLightColor: [0, 0, 0], 66 | models: models, 67 | panorama: panorama 68 | ) 69 | 70 | return scene 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Demos/Demos/SimpleScene/SimpleSceneMapView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import simd 3 | import CoreGraphicsSupport 4 | import SIMDSupport 5 | 6 | struct SimpleSceneMapView: View { 7 | @Binding 8 | var scene: SimpleScene 9 | 10 | let scale: CGFloat = 10 11 | 12 | var body: some View { 13 | Canvas(opaque: true) { context, size in 14 | context.concatenate(CGAffineTransform.translation(CGPoint(size.width / 2, size.height / 2))) 15 | for model in scene.models { 16 | let position = CGPoint(model.transform.translation.xz) 17 | let colorVector = (model.material as? FlatMaterial)?.baseColorFactor ?? [1, 0, 0, 1] 18 | let color = Color(red: Double(colorVector.r), green: Double(colorVector.g), blue: Double(colorVector.b)) 19 | context.fill(Path(ellipseIn: CGRect(center: position * scale, radius: 0.5 * scale)), with: .color(color.opacity(0.5))) 20 | } 21 | let cameraPosition = CGPoint(scene.camera.transform.translation.xz) 22 | 23 | if case let .perspective(perspective) = scene.camera.projection { 24 | // TODO: This is showing fovY but it should be fovX 25 | let viewCone = Path.arc(center: cameraPosition * scale, radius: 4 * scale, midAngle: .radians(Double(scene.camera.heading.radians)), width: .radians(Double(perspective.fovy.radians))) 26 | // context.fill(viewCone, with: .radialGradient(Gradient(colors: [.white.opacity(0.5), .white.opacity(0.0)]), center: cameraPosition * scale, startRadius: 0, endRadius: 4 * scale)) 27 | context.stroke(viewCone, with: .color(.white)) 28 | } 29 | 30 | var cameraImage = context.resolve(Image(systemName: "camera.circle.fill")) 31 | cameraImage.shading = .color(.mint) 32 | context.draw(cameraImage, at: cameraPosition * scale, anchor: .center) 33 | 34 | let lightPosition = CGPoint(scene.light.position.translation.xz) 35 | var lightImage = context.resolve(Image(systemName: "lightbulb.fill")) 36 | lightImage.shading = .color(.yellow) 37 | context.draw(lightImage, at: lightPosition * scale, anchor: .center) 38 | 39 | let targetPosition = cameraPosition + CGPoint(scene.camera.target.xz) 40 | var targetImage = context.resolve(Image(systemName: "scope")) 41 | targetImage.shading = .color(.white) 42 | context.draw(targetImage, at: targetPosition * scale, anchor: .center) 43 | } 44 | .background(.black) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Demos/Demos/SimpleScene/SimpleSceneRenderPass.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import ModelIO 3 | import Metal 4 | import MetalKit 5 | import SIMDSupport 6 | import RenderKitShaders 7 | import RenderKit 8 | import Observation 9 | import Everything 10 | 11 | protocol SceneRenderJob: RenderJob { 12 | var scene: SimpleScene { get set } 13 | var textureManager: TextureManager { get set } 14 | } 15 | 16 | class SimpleSceneRenderPass: RenderPass { 17 | var scene: SimpleScene { 18 | didSet { 19 | renderJobs.forEach { job in 20 | job.scene = scene 21 | } 22 | } 23 | } 24 | var renderJobs: [any RenderJob & SceneRenderJob] = [] 25 | 26 | var textureManager: TextureManager? 27 | 28 | init(scene: SimpleScene) { 29 | self.scene = scene 30 | } 31 | 32 | func setup(device: MTLDevice, configuration: inout Configuration) throws { 33 | let textureManager = TextureManager(device: device) 34 | self.textureManager = textureManager 35 | 36 | if let panorama = scene.panorama { 37 | let job = PanoramaRenderJob(scene: scene, textureManager: textureManager, panorama: panorama) 38 | job.scene = scene 39 | self.renderJobs.append(job) 40 | } 41 | 42 | let flatModels = scene.models.filter { ($0.material as? FlatMaterial) != nil } 43 | if !flatModels.isEmpty { 44 | let job = FlatMaterialRenderJob(scene: scene, textureManager: textureManager, models: flatModels) 45 | self.renderJobs.append(job) 46 | } 47 | 48 | let unlitModels = scene.models.filter { ($0.material as? UnlitMaterial) != nil } 49 | if !unlitModels.isEmpty { 50 | let job = UnlitMaterialRenderJob(scene: scene, textureManager: textureManager, models: unlitModels) 51 | self.renderJobs.append(job) 52 | } 53 | 54 | try self.renderJobs.forEach { job in 55 | try job.setup(device: device, configuration: &configuration) 56 | } 57 | } 58 | 59 | func draw(device: MTLDevice, size: CGSize, renderPassDescriptor: MTLRenderPassDescriptor, commandBuffer: MTLCommandBuffer) throws { 60 | try commandBuffer.withRenderCommandEncoder(descriptor: renderPassDescriptor) { encoder in 61 | encoder.label = "SimpleJobsBasedRenderPass-RenderCommandEncoder" 62 | try renderJobs.forEach { job in 63 | try job.encode(on: encoder, size: size) 64 | } 65 | } 66 | } 67 | } 68 | 69 | // MARK: - 70 | -------------------------------------------------------------------------------- /Demos/Demos/SimpleScene/YAMesh+Conversion.swift: -------------------------------------------------------------------------------- 1 | import RenderKit 2 | 3 | extension YAMesh { 4 | func convert(to other: VertexDescriptor) throws -> YAMesh { 5 | if vertexDescriptor == other { 6 | return self 7 | } 8 | 9 | print(vertexDescriptor) 10 | print(other) 11 | 12 | fatalError() 13 | } 14 | 15 | func pack(to other: VertexDescriptor) throws -> YAMesh { 16 | if vertexDescriptor == other { 17 | return self 18 | } 19 | 20 | print(vertexDescriptor) 21 | print(other) 22 | 23 | fatalError() 24 | } 25 | } 26 | 27 | extension VertexDescriptor { 28 | var isPacked: Bool { 29 | return layouts.count == 1 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Demos/Demos/Support/Bindings.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | 3 | // TODO: These parameter names are terrible. But this is a very useful function. 4 | // TODO: Want to validate types too if possible 5 | func resolveBindings (reflection: MTLRenderPipelineReflection, bindable: inout Bindable, _ a: [(WritableKeyPath, MTLFunctionType, String)]) { 6 | for (keyPath, shaderType, name) in a { 7 | switch shaderType { 8 | case .vertex: 9 | let binding = reflection.vertexBindings.first(where: { $0.name == name })! 10 | bindable[keyPath: keyPath] = binding.index 11 | case .fragment: 12 | let binding = reflection.fragmentBindings.first(where: { $0.name == name })! 13 | bindable[keyPath: keyPath] = binding.index 14 | default: 15 | fatalError() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demos/Demos/Support/Gradient.swift: -------------------------------------------------------------------------------- 1 | import simd 2 | import SwiftUI 3 | import Everything 4 | 5 | struct MyGradient { 6 | public struct Stop { 7 | public var color: SIMD4 8 | public var location: Float 9 | internal var controls: (Float, Float, Float) = (0, 0, 0) 10 | } 11 | 12 | var stops: [Stop] 13 | 14 | init(stops: [Stop]) { 15 | if stops.count == 1 { 16 | self.stops = stops 17 | return 18 | } 19 | assert(stops.first?.location == 0) 20 | assert(stops.last?.location == 1) 21 | self.stops = stops.enumerated().map { index, stop in 22 | var handle: (Float, Float, Float) = (stop.location, stop.location, stop.location) 23 | if index != 0 { 24 | handle.0 = stops[index - 1].location 25 | } 26 | if index != stops.count - 1 { 27 | handle.2 = stops[index + 1].location 28 | } 29 | var stop = stop 30 | stop.controls = handle 31 | return(stop) 32 | } 33 | // print(color(at: 0.5)) 34 | } 35 | } 36 | 37 | extension MyGradient { 38 | init(colors: [SIMD4]) { 39 | let delta = 1 / Float(colors.count - 1) 40 | self = .init(stops: colors.enumerated().map { index, color in 41 | Stop(color: color, location: Float(index) * delta) 42 | }) 43 | } 44 | } 45 | 46 | extension MyGradient { 47 | func color(at position: Float) -> SIMD4 { 48 | if stops.count == 1 { 49 | return stops.first!.color 50 | } 51 | let color = stops.reduce(SIMD4.zero) { result, stop in 52 | var factor: Float = 0 53 | if (stop.controls.0 ... stop.controls.1).contains(position) { 54 | factor = inverseLerp(value: position, startValue: stop.controls.0, endValue: stop.controls.1) 55 | } 56 | else if (stop.controls.1 ... stop.controls.2).contains(position) { 57 | factor = 1 - inverseLerp(value: position, startValue: stop.controls.1, endValue: stop.controls.2) 58 | } 59 | let color = stop.color * factor 60 | return result + color 61 | } 62 | 63 | return color 64 | } 65 | } 66 | 67 | //func clamp(_ value: T, _ lower: T, _ upper: T) -> T { 68 | // let max_ = max(value, lower) 69 | // let min_ = min(max_, upper) 70 | // return min_ 71 | //} 72 | 73 | func inverseLerp(value: Float, startValue: Float, endValue: Float) -> Float { 74 | let result: Float 75 | // if value <= startValue { 76 | // result = 0 77 | // } 78 | // else if value >= endValue { 79 | // result = 0 80 | // } 81 | // else { 82 | result = (value - startValue) / (endValue - startValue) 83 | // } 84 | return result 85 | } 86 | 87 | extension Color { 88 | init(_ color: SIMD4) { 89 | self = Color(red: Double(color[0]), green: Double(color[1]), blue: Double(color[2])) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Demos/Demos/Support/Logging.swift: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | let logger: Logger? = Logger() 4 | -------------------------------------------------------------------------------- /Demos/Demos/Support/Names.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // https://github.com/fnichol/names 4 | 5 | struct Names { 6 | static let shared = Names() 7 | 8 | let adjectives: [String.SubSequence] 9 | let nouns: [String.SubSequence] 10 | 11 | init() { 12 | do { 13 | adjectives = try String(contentsOf: Bundle.main.url(forResource: "adjectives", withExtension: "txt")!).split(whereSeparator: \.isNewline) 14 | nouns = try String(contentsOf: Bundle.main.url(forResource: "nouns", withExtension: "txt")!).split(whereSeparator: \.isNewline) 15 | } 16 | catch { 17 | fatalError("\(error)") 18 | } 19 | } 20 | 21 | func random(pad: Int? = nil) -> String { 22 | let adjective = adjectives.randomElement()! 23 | let noun = nouns.randomElement()! 24 | if let pad { 25 | let max = Int(pow(10, Double(pad))) 26 | let pad = Int.random(in: 1..(hashable value: Value, pad: Int? = nil) -> String where Value: Hashable { 35 | var value = abs(value.hashValue) 36 | let adjective = adjectives[value % adjectives.count] 37 | value /= adjectives.count 38 | let noun = nouns[value % nouns.count] 39 | value /= nouns.count 40 | if let pad { 41 | let padCount = Int(pow(10, Double(pad))) - 1 42 | let pad = value % padCount 43 | value /= padCount 44 | return "\(adjective)-\(noun)-\(pad)" 45 | } 46 | else { 47 | return "\(adjective)-\(noun)" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Demos/Demos/Support/Projections.swift: -------------------------------------------------------------------------------- 1 | import simd 2 | import SIMDSupport 3 | import SwiftUI 4 | 5 | public protocol ProjectionProtocol: Equatable, Sendable { 6 | func matrix(viewSize: SIMD2) -> simd_float4x4 7 | } 8 | 9 | public struct PerspectiveProjection: ProjectionProtocol { 10 | public var fovy: Angle 11 | public var zClip: ClosedRange 12 | 13 | public init(fovy: Angle, zClip: ClosedRange) { 14 | self.fovy = fovy 15 | self.zClip = zClip 16 | } 17 | 18 | public func matrix(viewSize: SIMD2) -> simd_float4x4 { 19 | let aspect = viewSize.x / viewSize.y 20 | return .perspective(aspect: aspect, fovy: Float(fovy.radians), near: zClip.lowerBound, far: zClip.upperBound) 21 | } 22 | } 23 | 24 | public struct OrthographicProjection: ProjectionProtocol { 25 | public var left: Float 26 | public var right: Float 27 | public var bottom: Float 28 | public var top: Float 29 | public var near: Float 30 | public var far: Float 31 | 32 | public init(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float) { 33 | self.left = left 34 | self.right = right 35 | self.bottom = bottom 36 | self.top = top 37 | self.near = near 38 | self.far = far 39 | } 40 | 41 | public func matrix(viewSize: SIMD2) -> simd_float4x4 { 42 | .orthographic(left: left, right: right, bottom: bottom, top: top, near: near, far: far) 43 | } 44 | } 45 | 46 | public enum Projection: ProjectionProtocol { 47 | case matrix(simd_float4x4) 48 | case perspective(PerspectiveProjection) 49 | case orthographic(OrthographicProjection) 50 | 51 | public func matrix(viewSize: SIMD2) -> simd_float4x4 { 52 | switch self { 53 | case .matrix(let projection): 54 | return projection 55 | case .perspective(let projection): 56 | return projection.matrix(viewSize: viewSize) 57 | case .orthographic(let projection): 58 | return projection.matrix(viewSize: viewSize) 59 | } 60 | } 61 | 62 | // TODO: Use that macro 63 | public enum Meta: CaseIterable { 64 | case matrix 65 | case perspective 66 | case orthographic 67 | } 68 | 69 | public var meta: Meta { 70 | switch self { 71 | case .matrix: 72 | return .matrix 73 | case .perspective: 74 | return .perspective 75 | case .orthographic: 76 | return .orthographic 77 | } 78 | } 79 | } 80 | 81 | extension Projection: Sendable { 82 | } 83 | -------------------------------------------------------------------------------- /Demos/Demos/Support/Resources.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | // TODO: all this is very experimental. 5 | 6 | public protocol ResourceProtocol: Hashable, Sendable { 7 | } 8 | 9 | public extension ResourceProtocol { 10 | func `as`(_ type: any URLProviding.Type) -> (any URLProviding)? { 11 | self as? any URLProviding 12 | } 13 | } 14 | 15 | public protocol URLProviding: ResourceProtocol { 16 | var url: URL { get throws } 17 | } 18 | 19 | public protocol SynchronousLoadable: ResourceProtocol { 20 | associatedtype Content 21 | associatedtype Parameter 22 | func load(_ parameter: Parameter) throws -> Content 23 | } 24 | 25 | public extension SynchronousLoadable where Parameter == () { 26 | func load() throws -> Content { 27 | return try load(()) 28 | } 29 | } 30 | 31 | public protocol AsynchronousLoadable: ResourceProtocol { 32 | associatedtype Content // TODO: Sendable? 33 | associatedtype Parameter 34 | func load(_ parameter: Parameter) async throws -> Content 35 | } 36 | 37 | public extension AsynchronousLoadable where Parameter == () { 38 | func load() async throws -> Content { 39 | return try await load(()) 40 | } 41 | } 42 | 43 | // MARK: - 44 | 45 | public enum BundleReference: Hashable, Sendable { 46 | enum Error: Swift.Error { 47 | case missingBundle 48 | } 49 | 50 | case main 51 | case byURL(URL) 52 | case byIdentifier(String) 53 | } 54 | 55 | public extension BundleReference { 56 | var exists: Bool { 57 | switch self { 58 | case .main: 59 | return true 60 | case .byURL(let url): 61 | return Bundle(url: url) != nil 62 | case .byIdentifier(let identifier): 63 | return Bundle(identifier: identifier) != nil 64 | } 65 | } 66 | 67 | var bundle: Bundle { 68 | get throws { 69 | switch self { 70 | case .main: 71 | return Bundle.main 72 | case .byURL(let url): 73 | guard let bundle = Bundle(url: url) else { 74 | throw Error.missingBundle 75 | } 76 | return bundle 77 | case .byIdentifier(let identifier): 78 | guard let bundle = Bundle(identifier: identifier) else { 79 | throw Error.missingBundle 80 | } 81 | return bundle 82 | } 83 | } 84 | } 85 | } 86 | 87 | // MARK: - 88 | 89 | public struct BundleResourceReference { 90 | public var bundle: BundleReference 91 | public var name: String 92 | public var `extension`: String? 93 | 94 | public init(bundle: BundleReference, name: String, `extension`: String? = nil) { 95 | self.bundle = bundle 96 | self.name = name 97 | self.`extension` = `extension` 98 | } 99 | 100 | enum Error: Swift.Error { 101 | case missingResource 102 | } 103 | 104 | public var exists: Bool { 105 | return (try? bundle.bundle.url(forResource: name, withExtension: `extension`)) != nil 106 | } 107 | 108 | public var url: URL { 109 | get throws { 110 | guard let url = try bundle.bundle.url(forResource: name, withExtension: `extension`) else { 111 | throw Error.missingResource 112 | } 113 | return url 114 | } 115 | } 116 | } 117 | 118 | public extension BundleReference { 119 | func resource(named name: String, withExtension `extension`: String?) -> BundleResourceReference { 120 | return BundleResourceReference(bundle: self, name: name, extension: `extension`) 121 | } 122 | } 123 | 124 | // MARK: - 125 | 126 | extension BundleResourceReference: URLProviding { 127 | } 128 | -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_01.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_01.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_02.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_02.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_03.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_03.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_04.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_04.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_05.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_05.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_06.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_06.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_07.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_07.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_08.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_08.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_09.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_09.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_10.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_10.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_11.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_11.ktx -------------------------------------------------------------------------------- /Demos/Demos/TestcardTiles/Testcard_12.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/TestcardTiles/Testcard_12.ktx -------------------------------------------------------------------------------- /Demos/Demos/Volumetric/StanfordVolumeData.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Demos/Demos/Volumetric/StanfordVolumeData.tar -------------------------------------------------------------------------------- /Demos/Packages/Compute/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Demos/Packages/Compute/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demos/Packages/Compute/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Compute", 7 | platforms: [ 8 | .iOS(.v17), 9 | .macOS(.v14), 10 | .macCatalyst(.v17), 11 | .tvOS(.v17), 12 | .visionOS(.v1), 13 | ], 14 | products: [ 15 | .library(name: "Compute", 16 | targets: ["Compute"]), 17 | ], 18 | dependencies: [ 19 | .package(url: "https://github.com/schwa/SwiftGraphics", branch: "jwight/develop"), 20 | .package(path: "../../.."), 21 | ], 22 | targets: [ 23 | .target(name: "Compute", dependencies: [ 24 | .product(name: "MetalSupport", package: "SwiftGraphics"), 25 | .product(name: "CoreGraphicsSupport", package: "SwiftGraphics"), 26 | .product(name: "RenderKit", package: "RenderKit"), 27 | ]), 28 | .executableTarget(name: "ComputeTool", dependencies: ["Compute"], resources: [ 29 | .process("BitonicSort.metal"), 30 | .process("GameOfLife.metal"), 31 | .process("RandomFill.metal"), 32 | ]), 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /Demos/Packages/Compute/Sources/Compute/ShaderFunction.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | 3 | @dynamicMemberLookup 4 | public struct ShaderLibrary { 5 | public static var `default` = ShaderLibrary.bundle(.main) 6 | 7 | public static func bundle(_ bundle: Bundle) -> Self { 8 | return Self(bundle: bundle) 9 | } 10 | 11 | var bundle: Bundle 12 | 13 | private init(bundle: Bundle) { 14 | self.bundle = bundle 15 | } 16 | 17 | public subscript(dynamicMember name: String) -> ShaderFunction { 18 | return ShaderFunction(library: self, name: name) 19 | } 20 | } 21 | 22 | public extension MTLDevice { 23 | func makeLibrary(_ library: ShaderLibrary) throws -> MTLLibrary { 24 | return try makeDefaultLibrary(bundle: library.bundle) 25 | } 26 | } 27 | 28 | // MARK: - 29 | 30 | public struct ShaderFunction: Identifiable { 31 | public let id = UUID() 32 | public let library: ShaderLibrary 33 | public let name: String 34 | public let constants: [ShaderConstant] 35 | 36 | public init(library: ShaderLibrary, name: String, constants: [ShaderConstant] = []) { 37 | self.library = library 38 | self.name = name 39 | self.constants = constants 40 | 41 | // MTLFunctionConstantValues 42 | // MTLDataType 43 | } 44 | } 45 | 46 | public struct ShaderConstant { 47 | var dataType: MTLDataType 48 | var accessor: ((UnsafeRawPointer) -> Void) -> Void 49 | 50 | public init(dataType: MTLDataType, value: [T]) { 51 | self.dataType = dataType 52 | accessor = { (callback: (UnsafeRawPointer) -> Void) in 53 | value.withUnsafeBytes { pointer in 54 | callback(pointer.baseAddress!) 55 | } 56 | } 57 | } 58 | 59 | public init(dataType: MTLDataType, value: T) { 60 | self.dataType = dataType 61 | accessor = { (callback: (UnsafeRawPointer) -> Void) in 62 | withUnsafeBytes(of: value) { pointer in 63 | callback(pointer.baseAddress!) 64 | } 65 | } 66 | } 67 | 68 | public func add(to values: MTLFunctionConstantValues, name: String) { 69 | accessor { pointer in 70 | values.setConstantValue(pointer, type: dataType, withName: name) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Demos/Packages/Compute/Sources/ComputeTool/BitonicSort.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | [[kernel]] 7 | void bitonicSort( 8 | uint3 thread_position_in_grid [[thread_position_in_grid]], 9 | constant uint &numEntries [[buffer(0)]], 10 | constant uint &groupWidth [[buffer(1)]], 11 | constant uint &groupHeight [[buffer(2)]], 12 | constant uint &stepIndex [[buffer(3)]], 13 | device uint *entries [[buffer(4)]] 14 | ) { 15 | const auto index = thread_position_in_grid.x; 16 | const auto hIndex = index & (groupWidth - 1); 17 | const auto indexLeft = hIndex + (groupHeight + 1) * (index / groupWidth); 18 | const auto stepSize = stepIndex == 0 ? groupHeight - 2 * hIndex : (groupHeight + 1) / 2; 19 | const auto indexRight = indexLeft + stepSize; 20 | // Exit if out of bounds (for non-power of 2 input sizes) 21 | if (indexRight >= numEntries) { 22 | return; 23 | } 24 | const auto valueLeft = entries[indexLeft]; 25 | const auto valueRight = entries[indexRight]; 26 | // Swap entries if value is descending 27 | if (valueLeft > valueRight) { 28 | entries[indexLeft] = valueRight; 29 | entries[indexRight] = valueLeft; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Demos/Packages/Compute/Sources/ComputeTool/BitonicSort.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | import Compute 3 | 4 | public struct BitonicSortDemo { 5 | public init() { 6 | } 7 | 8 | public func main() throws { 9 | let stopWatch = StopWatch() 10 | 11 | print("Creating random buffer", stopWatch) 12 | var entries: [UInt32] = (0..<100_000).shuffled() 13 | 14 | print("Copying buffer to GPU.", stopWatch) 15 | let device = MTLCreateSystemDefaultDevice()! 16 | let numEntries = entries.count 17 | let buffer = entries.withUnsafeMutableBufferPointer { buffer in 18 | let buffer = UnsafeMutableRawBufferPointer(buffer) 19 | return device.makeBuffer(bytes: buffer.baseAddress!, length: buffer.count)! 20 | } 21 | print("Preparing compute.", stopWatch) 22 | 23 | print(Bundle.module.bundlePath) 24 | 25 | let function = ShaderLibrary.bundle(.module).bitonicSort 26 | let numStages = Int(log2(nextPowerOfTwo(Double(numEntries)))) 27 | 28 | let compute = try Compute(device: device) 29 | 30 | var pass = try compute.makePass(function: function, arguments: [ 31 | "numEntries": .int(numEntries), 32 | "entries": .buffer(buffer), 33 | ]) 34 | 35 | let start = CFAbsoluteTimeGetCurrent() 36 | print("Running \(numStages) compute stages", stopWatch) 37 | 38 | var threadgroupsPerGrid = (entries.count + pass.maxTotalThreadsPerThreadgroup - 1) / pass.maxTotalThreadsPerThreadgroup 39 | threadgroupsPerGrid = (threadgroupsPerGrid + pass.threadExecutionWidth - 1) / pass.threadExecutionWidth * pass.threadExecutionWidth 40 | 41 | try compute.task { task in 42 | try task { dispatch in 43 | for stageIndex in 0.. 2 | #include 3 | 4 | using namespace metal; 5 | 6 | constant bool wrap [[function_constant(0)]]; 7 | 8 | static constant int2 positions[] = { 9 | int2(-1, -1), 10 | int2( 0, -1), 11 | int2(+1, -1), 12 | int2(-1, 0), 13 | int2(+1, 0), 14 | int2(-1, +1), 15 | int2( 0, +1), 16 | int2(+1, +1), 17 | }; 18 | 19 | bool rules(uint count, bool alive) { 20 | if (alive == true && (count == 2 || count == 3)) { 21 | return true; 22 | } 23 | else if (alive == false && count == 3) { 24 | return true; 25 | } 26 | else if (alive == true) { 27 | return false; 28 | } 29 | else { 30 | return false; 31 | } 32 | } 33 | 34 | template void gameOfLifeGENERIC( 35 | uint2 gid, 36 | texture2d inputTexture, 37 | texture2d outputTexture, 38 | V clear, 39 | V set 40 | ) 41 | { 42 | const int2 sgid = int2(gid); 43 | const int2 inputTextureSize = int2(inputTexture.get_width(), inputTexture.get_height()); 44 | if (sgid.x >= inputTextureSize.x || sgid.y >= inputTextureSize.y) { 45 | return; 46 | } 47 | uint count = 0; 48 | for (int N = 0; N != 8; ++N) { 49 | int2 position = sgid + positions[N]; 50 | if (!wrap) { 51 | if (position.x < 0 || position.x >= inputTextureSize.x) { 52 | continue; 53 | } 54 | else if (position.y < 0 || position.y >= inputTextureSize.y) { 55 | continue; 56 | } 57 | } 58 | else { 59 | position.x = (position.x + inputTextureSize.x) % inputTextureSize.x; 60 | position.y = (position.y + inputTextureSize.y) % inputTextureSize.y; 61 | } 62 | count += inputTexture.read(uint2(position)).r ? 1 : 0; 63 | } 64 | const bool alive = inputTexture.read(gid).r != 0; 65 | outputTexture.write(rules(count, alive), gid); 66 | } 67 | 68 | 69 | [[kernel]] 70 | void gameOfLife_uint( 71 | uint2 gid [[thread_position_in_grid]], 72 | texture2d inputTexture [[texture(0)]], 73 | texture2d outputTexture [[texture(1)]]) 74 | { 75 | gameOfLifeGENERIC(gid, inputTexture, outputTexture, 0, 1); 76 | } 77 | 78 | [[kernel]] 79 | void gameOfLife_float4( 80 | uint2 gid [[thread_position_in_grid]], 81 | texture2d inputTexture [[texture(0)]], 82 | texture2d outputTexture [[texture(1)]]) 83 | { 84 | gameOfLifeGENERIC(gid, inputTexture, outputTexture, float4(0, 0, 0, 0), float4(1, 1, 1, 1)); 85 | } 86 | -------------------------------------------------------------------------------- /Demos/Packages/Compute/Sources/ComputeTool/GameOfLife.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | import Foundation 3 | import Compute 4 | import AVFoundation 5 | 6 | struct GameOfLife { 7 | let width = 16 8 | let height = 16 9 | let device = MTLCreateSystemDefaultDevice()! 10 | 11 | func main() throws { 12 | let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .r8Uint, width: width, height: height, mipmapped: false) 13 | textureDescriptor.usage = [.shaderRead, .shaderWrite] 14 | let textureA = device.makeTexture(descriptor: textureDescriptor)! 15 | textureA.label = "texture-a" 16 | let textureB = device.makeTexture(descriptor: textureDescriptor)! 17 | textureB.label = "texture-b" 18 | let compute = try Compute(device: device) 19 | let library = ShaderLibrary.bundle(.module) 20 | var randomFillPass = try compute.makePass(function: library.randomFill_uint) 21 | randomFillPass.arguments.outputTexture = .texture(textureA) 22 | 23 | var gameOfLifePassA = try compute.makePass(function: library.gameOfLife_uint, constants: ["wrap": .bool(false)]) 24 | gameOfLifePassA.arguments.inputTexture = .texture(textureA) 25 | gameOfLifePassA.arguments.outputTexture = .texture(textureB) 26 | // print(gameOfLifePassA) 27 | 28 | var gameOfLifePassB = gameOfLifePassA 29 | gameOfLifePassB.arguments.inputTexture = .texture(textureB) 30 | gameOfLifePassB.arguments.outputTexture = .texture(textureA) 31 | 32 | let assetWriter = try AVAssetWriter(outputURL: URL(filePath: "1234.mp4"), fileType: .mp4) 33 | 34 | let assetWriterVideoInput = AVAssetWriterInput(mediaType: .video, outputSettings: [ 35 | AVVideoCodecKey: AVVideoCodecType.h264, 36 | AVVideoWidthKey: width, 37 | AVVideoHeightKey: height, 38 | ]) 39 | assetWriterVideoInput.expectsMediaDataInRealTime = false 40 | assetWriter.add(assetWriterVideoInput) 41 | 42 | let assetWriterPixelBufferInput = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterVideoInput, sourcePixelBufferAttributes: [ 43 | kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, 44 | kCVPixelBufferWidthKey as String: width, 45 | kCVPixelBufferHeightKey as String: height, 46 | kCVPixelBufferMetalCompatibilityKey as String: true 47 | ]) 48 | 49 | assetWriter.startWriting() 50 | 51 | print(assetWriterPixelBufferInput) 52 | // guard let pixelBufferPool = assetWriterPixelBufferInput.pixelBufferPool else { 53 | // fatalError() 54 | // } 55 | // print(pixelBufferPool) 56 | 57 | try compute.task { task in 58 | try task { dispatch in 59 | try dispatch(pass: randomFillPass, threadgroupsPerGrid: MTLSize(width: width, height: height, depth: 1), threadsPerThreadgroup: MTLSize(width: 1, height: 1, depth: 1)) 60 | } 61 | } 62 | 63 | for _ in 0..<10 { 64 | try compute.task { task in 65 | try task { dispatch in 66 | try dispatch(pass: gameOfLifePassA, threadgroupsPerGrid: MTLSize(width: width, height: height, depth: 1), threadsPerThreadgroup: MTLSize(width: 1, height: 1, depth: 1)) 67 | } 68 | } 69 | 70 | try compute.task { task in 71 | try task { dispatch in 72 | try dispatch(pass: gameOfLifePassA, threadgroupsPerGrid: MTLSize(width: width, height: height, depth: 1), threadsPerThreadgroup: MTLSize(width: 1, height: 1, depth: 1)) 73 | } 74 | } 75 | } 76 | 77 | print(textureA.toString()) 78 | print(textureB.toString()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Demos/Packages/Compute/Sources/ComputeTool/RandomFill.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | float random(float2 p) 7 | { 8 | // We need irrationals for pseudo randomness. 9 | // Most (all?) known transcendental numbers will (generally) work. 10 | const float2 r = float2( 11 | 23.1406926327792690, // e^pi (Gelfond's constant) 12 | 2.6651441426902251); // 2^sqrt(2) (Gelfond–Schneider constant) 13 | return fract(cos(fmod(123456789.0, 1e-7 + 256.0 * dot(p,r)))); 14 | } 15 | 16 | [[kernel]] 17 | void randomFill_uint( 18 | uint2 thread_position_in_grid [[thread_position_in_grid]], 19 | texture2d outputTexture [[texture(1)]]) 20 | { 21 | const float2 id = float2(thread_position_in_grid); 22 | outputTexture.write(random(id) > 0.5 ? 255 : 0, thread_position_in_grid); 23 | } 24 | 25 | [[kernel]] 26 | void randomFill_float( 27 | uint2 thread_position_in_grid [[thread_position_in_grid]], 28 | texture2d outputTexture [[texture(1)]]) 29 | { 30 | const float2 id = float2(thread_position_in_grid); 31 | outputTexture.write(random(id) > 0.5 ? 1 : 0, thread_position_in_grid); 32 | } 33 | -------------------------------------------------------------------------------- /Demos/Packages/Compute/Sources/ComputeTool/Support.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | import Foundation 3 | import Compute 4 | import MetalSupport 5 | import CoreGraphicsSupport 6 | import CoreGraphics 7 | import AppKit 8 | import RenderKit 9 | 10 | class StopWatch: CustomStringConvertible { 11 | var last: CFAbsoluteTime? 12 | 13 | var time: CFAbsoluteTime { 14 | let now = CFAbsoluteTimeGetCurrent() 15 | if last == nil { 16 | last = now 17 | } 18 | return now - last! 19 | } 20 | 21 | var description: String { 22 | return "\(time)" 23 | } 24 | } 25 | 26 | func time(_ block: () -> Void) -> CFAbsoluteTime { 27 | let start = CFAbsoluteTimeGetCurrent() 28 | block() 29 | let end = CFAbsoluteTimeGetCurrent() 30 | return end - start 31 | } 32 | 33 | public func nextPowerOfTwo(_ value: Double) -> Double { 34 | let logValue = log2(Double(value)) 35 | let nextPower = pow(2.0, ceil(logValue)) 36 | return nextPower 37 | } 38 | 39 | public func nextPowerOfTwo(_ value: Int) -> Int { 40 | return Int(nextPowerOfTwo(Double(value))) 41 | } 42 | 43 | extension Collection where Element: Comparable { 44 | var isSorted: Bool { 45 | return zip(self, sorted()).allSatisfy { lhs, rhs in 46 | lhs == rhs 47 | } 48 | } 49 | } 50 | 51 | public extension MTLSize { 52 | init(width: Int) { 53 | self = MTLSize(width: width, height: 1, depth: 1) 54 | } 55 | } 56 | 57 | extension MTLTexture { 58 | func toString() -> String { 59 | assert(pixelFormat == .r8Uint) 60 | assert(depth == 1) 61 | 62 | let size = width * height * depth 63 | 64 | // TODO: Assumes width is aligned correctly 65 | var buffer = Array(repeating: UInt8.zero, count: size) 66 | 67 | buffer.withUnsafeMutableBytes { buffer in 68 | getBytes(buffer.baseAddress!, bytesPerRow: width, from: MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize(width: width, height: height, depth: depth)), mipmapLevel: 0) 69 | } 70 | 71 | var s = "" 72 | for row in 0.. CGImage? { 84 | guard let context = CGContext.bitmapContext(definition: .init(width: width, height: height, pixelFormat: .rgba8)) else { 85 | return nil 86 | } 87 | let rect = CGRect(width: CGFloat(width), height: CGFloat(height)) 88 | let size2 = rect.size / 2 89 | context.setFillColor(CGColor(red: 1, green: 0, blue: 0, alpha: 1)) 90 | context.fill([CGRect(origin: rect.minXMinY, size: size2)]) 91 | context.setFillColor(CGColor(red: 0, green: 1, blue: 0, alpha: 1)) 92 | context.fill([CGRect(origin: rect.midXMinY, size: size2)]) 93 | context.setFillColor(CGColor(red: 0, green: 0, blue: 1, alpha: 1)) 94 | context.fill([CGRect(origin: rect.minXMidY, size: size2)]) 95 | 96 | var locations: [CGFloat] = [0, 1] 97 | let colors = [CGColor(red: 1, green: 1, blue: 1, alpha: 0), CGColor(red: 1, green: 1, blue: 1, alpha: 1)] 98 | let gradient = CGGradient(colorsSpace: context.colorSpace!, colors: colors as CFArray, locations: &locations)! 99 | 100 | context.clip(to: [CGRect(origin: rect.midXMidY, size: size2)]) 101 | context.drawLinearGradient(gradient, start: rect.midXMidY, end: rect.maxXMaxY, options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) 102 | return context.makeImage() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Demos/Packages/DemosSupport/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /Demos/Packages/DemosSupport/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "DemosSupport", 8 | platforms: [ 9 | .iOS(.v17), 10 | .macOS(.v14), 11 | .macCatalyst(.v17), 12 | .tvOS(.v17), 13 | .visionOS(.v1), 14 | ], 15 | products: [ 16 | // Products define the executables and libraries a package produces, making them visible to other packages. 17 | .library( 18 | name: "DemosSupport", 19 | targets: ["DemosSupport"]), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package, defining a module or a test suite. 23 | // Targets can depend on other targets in this package and products from dependencies. 24 | .target( 25 | name: "DemosSupport"), 26 | .testTarget( 27 | name: "DemosSupportTests", 28 | dependencies: ["DemosSupport"]), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /Demos/Packages/DemosSupport/Tests/DemosSupportTests/DemosSupportTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import DemosSupport 3 | 4 | final class SpatialLookupTests: XCTestCase { 5 | func test1() throws { 6 | var table = SpatialLookupTable<[SIMD2]>(size: [100, 100], positions: []) 7 | table.update(points: [ 8 | [0, 0], [100, 100] 9 | ], radius: 50) 10 | 11 | XCTAssertEqual(table.indicesNear(point: [0, 0]).sorted(), [0]) 12 | XCTAssertEqual(table.indicesNear(point: [50, 50]).sorted(), []) 13 | XCTAssertEqual(table.indicesNear(point: [100, 100]).sorted(), [1]) 14 | 15 | table.update(points: [ 16 | [0, 0], [10, 10] 17 | ], radius: 50) 18 | XCTAssertEqual(table.indicesNear(point: [0, 0]).sorted(), [0, 1]) 19 | XCTAssertEqual(table.indicesNear(point: [50, 50]).sorted(), []) 20 | XCTAssertEqual(table.indicesNear(point: [100, 100]).sorted(), []) 21 | 22 | table.update(points: [ 23 | [0, 0], [0, 0] 24 | ], radius: 50) 25 | XCTAssertEqual(table.indicesNear(point: [0, 0]).sorted(), [0, 1]) 26 | XCTAssertEqual(table.indicesNear(point: [50, 50]).sorted(), []) 27 | XCTAssertEqual(table.indicesNear(point: [100, 100]).sorted(), []) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Demos/RenderKitDemos.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demos/RenderKitDemos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demos/RenderKitDemos.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demos/RenderKitDemos.xcodeproj/xcshareddata/xcschemes/Demos.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Demos/xcconfig/CLI-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // CLI-Debug.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | #include "CLI-Shared.xcconfig" 9 | 10 | //********************************************// 11 | //* Currently no build settings in this file *// 12 | //********************************************// 13 | -------------------------------------------------------------------------------- /Demos/xcconfig/CLI-Release.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // CLI-Release.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | #include "CLI-Shared.xcconfig" 9 | 10 | //********************************************// 11 | //* Currently no build settings in this file *// 12 | //********************************************// 13 | -------------------------------------------------------------------------------- /Demos/xcconfig/CLI-Shared.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // CLI-Shared.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | CODE_SIGN_STYLE = Automatic 9 | DEVELOPMENT_TEAM = 6E23EP94PG 10 | ENABLE_HARDENED_RUNTIME = YES 11 | MACOSX_DEPLOYMENT_TARGET = 14.0 12 | PRODUCT_NAME = $(TARGET_NAME) 13 | SDKROOT = macosx 14 | SWIFT_VERSION = 5.0 15 | -------------------------------------------------------------------------------- /Demos/xcconfig/Demos-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Demos-Debug.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | #include "Demos-Shared.xcconfig" 9 | 10 | //********************************************// 11 | //* Currently no build settings in this file *// 12 | //********************************************// 13 | -------------------------------------------------------------------------------- /Demos/xcconfig/Demos-Release.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Demos-Release.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | #include "Demos-Shared.xcconfig" 9 | 10 | //********************************************// 11 | //* Currently no build settings in this file *// 12 | //********************************************// 13 | -------------------------------------------------------------------------------- /Demos/xcconfig/Demos-Shared.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Demos-Shared.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon 9 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor 10 | CODE_SIGN_ENTITLEMENTS = Demos/Demos.entitlements 11 | CODE_SIGN_STYLE = Automatic 12 | CURRENT_PROJECT_VERSION = 1 13 | DEVELOPMENT_ASSET_PATHS = "Demos/Preview Content" 14 | DEVELOPMENT_TEAM = 6E23EP94PG 15 | ENABLE_HARDENED_RUNTIME = YES 16 | ENABLE_PREVIEWS = YES 17 | GENERATE_INFOPLIST_FILE = YES 18 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*] = YES 19 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*] = YES 20 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*] = YES 21 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*] = YES 22 | INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*] = YES 23 | INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*] = YES 24 | INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*] = UIStatusBarStyleDefault 25 | INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*] = UIStatusBarStyleDefault 26 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight 27 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight 28 | IPHONEOS_DEPLOYMENT_TARGET = 17.0 29 | LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks 30 | LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = @executable_path/../Frameworks 31 | MACOSX_DEPLOYMENT_TARGET = 14.0 32 | MARKETING_VERSION = 1.0 33 | PRODUCT_BUNDLE_IDENTIFIER = io.schwa.Demos 34 | PRODUCT_NAME = $(TARGET_NAME) 35 | SDKROOT = auto 36 | SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx 37 | SWIFT_EMIT_LOC_STRINGS = YES 38 | SWIFT_VERSION = 5.0 39 | 40 | SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx xros xrsimulator 41 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight 42 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight 43 | INFOPLIST_FILE = Demos/Info.plist 44 | SUPPORTS_MACCATALYST = NO 45 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO 46 | SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES 47 | TARGETED_DEVICE_FAMILY = 1,2,7 48 | -------------------------------------------------------------------------------- /Demos/xcconfig/Project-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Project-Debug.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | #include "Project-Shared.xcconfig" 9 | 10 | DEBUG_INFORMATION_FORMAT = dwarf 11 | ENABLE_TESTABILITY = YES 12 | GCC_DYNAMIC_NO_PIC = NO 13 | GCC_OPTIMIZATION_LEVEL = 0 14 | GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) 15 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE 16 | ONLY_ACTIVE_ARCH = YES 17 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG $(inherited) 18 | SWIFT_OPTIMIZATION_LEVEL = -Onone 19 | -------------------------------------------------------------------------------- /Demos/xcconfig/Project-Release.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Project-Release.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | #include "Project-Shared.xcconfig" 9 | 10 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 11 | ENABLE_NS_ASSERTIONS = NO 12 | MTL_ENABLE_DEBUG_INFO = NO 13 | SWIFT_COMPILATION_MODE = wholemodule 14 | -------------------------------------------------------------------------------- /Demos/xcconfig/Project-Shared.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Project-Shared.xcconfig 3 | // 4 | // Generated by BuildSettingExtractor on 6/27/23 5 | // https://buildsettingextractor.com 6 | // 7 | 8 | ALWAYS_SEARCH_USER_PATHS = NO 9 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES 10 | CLANG_ANALYZER_NONNULL = YES 11 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE 12 | CLANG_CXX_LANGUAGE_STANDARD = gnu++20 13 | CLANG_ENABLE_MODULES = YES 14 | CLANG_ENABLE_OBJC_ARC = YES 15 | CLANG_ENABLE_OBJC_WEAK = YES 16 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 17 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES 18 | CLANG_WARN_BOOL_CONVERSION = YES 19 | CLANG_WARN_COMMA = YES 20 | CLANG_WARN_CONSTANT_CONVERSION = YES 21 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES 22 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR 23 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES 24 | CLANG_WARN_EMPTY_BODY = YES 25 | CLANG_WARN_ENUM_CONVERSION = YES 26 | CLANG_WARN_INFINITE_RECURSION = YES 27 | CLANG_WARN_INT_CONVERSION = YES 28 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES 29 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 30 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES 31 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR 32 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES 33 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES 34 | CLANG_WARN_STRICT_PROTOTYPES = YES 35 | CLANG_WARN_SUSPICIOUS_MOVE = YES 36 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 37 | CLANG_WARN_UNREACHABLE_CODE = YES 38 | COPY_PHASE_STRIP = NO 39 | ENABLE_STRICT_OBJC_MSGSEND = YES 40 | ENABLE_USER_SCRIPT_SANDBOXING = YES 41 | GCC_C_LANGUAGE_STANDARD = gnu17 42 | GCC_NO_COMMON_BLOCKS = YES 43 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES 44 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR 45 | GCC_WARN_UNDECLARED_SELECTOR = YES 46 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE 47 | GCC_WARN_UNUSED_FUNCTION = YES 48 | GCC_WARN_UNUSED_VARIABLE = YES 49 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES 50 | MTL_FAST_MATH = YES 51 | 52 | DEAD_CODE_STRIPPING = YES 53 | SWIFT_STRICT_CONCURRENCY = complete 54 | -------------------------------------------------------------------------------- /Documentation/Exported PNG image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Documentation/Exported PNG image.png -------------------------------------------------------------------------------- /Documentation/Mesh.plantuml: -------------------------------------------------------------------------------- 1 | @startuml Meshes 2 | 3 | package MetalKit { 4 | class MTKMesh { 5 | + submeshes: [MTKSubmesh] 6 | + vertexBuffers: [MTKMeshBuffer] 7 | + vertexDescriptor: MTLVertexDescriptor 8 | + vertexCount: Int 9 | + name: String 10 | + init(mesh:,device:) 11 | + class func newMeshes(asset:,device:) 12 | 13 | } 14 | 15 | class MTKSubmesh { 16 | + indexBuffer: MTKMeshBuffer 17 | + indexCount: Int 18 | + indexType: MTLIndexType 19 | + mesh: MTKMesh 20 | + name: String 21 | + primitiveType: MTLPrimitiveType 22 | } 23 | 24 | class MTKMeshBuffer { 25 | + allocator: MTKMeshBufferAllocator 26 | + buffer: MTLBuffer 27 | + offset: Int 28 | + length: Int 29 | + type: MTKMeshBufferType 30 | } 31 | 32 | MTKMesh --> MTKSubmesh : submeshes 33 | MTKMesh --> MTKMeshBuffer : vertexBuffers 34 | MTKSubmesh --> MTKMeshBuffer : indexBuffer 35 | } 36 | 37 | package ModelIO { 38 | class MDLMesh { 39 | + submeshes: [MDLSubmesh] 40 | + vertexBuffers: [MDLMeshBuffer] 41 | + vertexDescriptor: MDLVertexDescriptor 42 | + vertexCount: Int 43 | + allocator: MDLMeshBufferAllocator 44 | + boundingBox: MDLAxisAlignedBoundingBox 45 | } 46 | 47 | class MDLSubmesh { 48 | + indexBuffer: MDLMeshBuffer 49 | + indexCount: Int 50 | + indexType: MDLIndexBitDepth 51 | + geometryType: MDLGeometryType 52 | + topology: MDLSubmeshTopology? 53 | + material: MDLMaterial? 54 | + name: String 55 | } 56 | 57 | class MDLMeshBuffer { 58 | + allocator: MDLMeshBufferAllocator 59 | + length: Int 60 | + type: MDLMeshBufferType 61 | + zone: MDLMeshBufferZone 62 | } 63 | 64 | MDLMesh --> MDLSubmesh : submeshes 65 | MDLMesh --> MDLMeshBuffer : vertexBuffers 66 | MDLSubmesh --> MDLMeshBuffer : indexBuffer 67 | 68 | } 69 | @enduml 70 | -------------------------------------------------------------------------------- /Documentation/Screenshot 2023-07-01 at 08.06.29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Documentation/Screenshot 2023-07-01 at 08.06.29.png -------------------------------------------------------------------------------- /Documentation/Screenshot 2023-09-19 at 19.51.00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Documentation/Screenshot 2023-09-19 at 19.51.00.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Jonathan Wight 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /MyPlayground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RenderKitScratch 3 | import simd 4 | 5 | let sphere = Sphere(center: .zero, radius: 8) 6 | print(try sphere.encodeToShapeScript()) 7 | 8 | let line = Line3D(point: [0, 0, 0], direction: [1, 0, 0]) 9 | 10 | print(try line.encodeToShapeScript()) 11 | -------------------------------------------------------------------------------- /MyPlayground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "RenderKit", 7 | platforms: [ 8 | .iOS(.v17), 9 | .macOS(.v14), 10 | .macCatalyst(.v17), 11 | .tvOS(.v17), 12 | .visionOS(.v1), 13 | ], 14 | products: [ 15 | .library(name: "RenderKit", targets: ["RenderKit"]), 16 | .library(name: "RenderKitScratch", targets: ["RenderKitScratch"]), 17 | .library(name: "RenderKitShaders", targets: ["RenderKitShaders"]), 18 | ], 19 | dependencies: [ 20 | .package(url: "https://github.com/schwa/Everything", branch: "jwight/downsizing"), 21 | .package(url: "https://github.com/schwa/SwiftGraphics", branch: "jwight/develop"), 22 | .package(url: "https://github.com/apple/swift-algorithms", from: "1.1.0"), 23 | .package(url: "https://github.com/apple/swift-async-algorithms", from: "0.1.0"), 24 | .package(url: "https://github.com/schwa/swiftformats", from: "0.3.3"), 25 | .package(url: "https://github.com/schwa/swiftfields", from: "0.1.3"), 26 | .package(url: "https://github.com/schwa/MetalCompilerPlugin", from: "0.0.2"), 27 | //.package(url: "https://github.com/schwa/StreamBuilder", branch: "main"), 28 | ], 29 | targets: [ 30 | .target( 31 | name: "RenderKit", 32 | dependencies: [ 33 | .product(name: "MetalSupport", package: "SwiftGraphics"), 34 | .product(name: "MetalSupportUnsafeConformances", package: "SwiftGraphics"), 35 | .product(name: "SIMDSupport", package: "SwiftGraphics"), 36 | .product(name: "Shapes2D", package: "SwiftGraphics"), 37 | .product(name: "Everything", package: "Everything"), 38 | .product(name: "CoreGraphicsSupport", package: "SwiftGraphics"), 39 | .product(name: "Algorithms", package: "swift-algorithms"), 40 | .product(name: "SwiftFields", package: "swiftfields"), 41 | .product(name: "SwiftFormats", package: "swiftformats"), 42 | .product(name: "AsyncAlgorithms", package: "swift-async-algorithms"), 43 | "RenderKitShaders", 44 | // .product(name: "StreamBuilder", package: "StreamBuilder"), 45 | ], 46 | resources: [ 47 | // .process("Media.xcassets"), 48 | .process("VisionOS/Assets.xcassets"), 49 | ], 50 | swiftSettings: [ 51 | .enableExperimentalFeature("VariadicGenerics"), 52 | .enableUpcomingFeature("StrictConcurrency"), 53 | ] 54 | ), 55 | .target( 56 | name: "RenderKitShaders", 57 | plugins: [ 58 | // .plugin(name: "MetalCompilerPlugin", package: "MetalCompilerPlugin") 59 | ] 60 | ), 61 | .target( 62 | name: "RenderKitScratch", 63 | dependencies: [ 64 | "Everything", 65 | "RenderKit", 66 | ] 67 | ), 68 | .testTarget( 69 | name: "RenderKitTests", 70 | dependencies: ["RenderKit", "RenderKitScratch"]), 71 | ] 72 | ) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RenderKit 2 | 3 | Yet another Metal Rendering engine experiment… 4 | 5 | ## Assumptions 6 | 7 | Post-multiplication, Column-Major, Right-handed, Y up 8 | 9 | ## Screenshots 10 | 11 | ![Latest Screenshot]() 12 | ![Older Screenshot]() 13 | 14 | ## Goals 15 | 16 | ### Longer Term 17 | 18 | * Tie into WASD/Game Controller system (from RenderKit - move code to Everything) 19 | * Render the position of lights 20 | * Learn from [RenderKit2](https://github.com/schwa/RenderKit/tree/RenderKit2) & [RenderKitClassic](https://github.com/schwa/RenderKit/tree/RenderKitClassic) 21 | * More type-safety. Especially pipeline attributes 22 | * Async. (Good luck) 23 | * Outputs 24 | * A unified "renderable" system. Can render to any of multiple locations - MTKView, "raw" CAMetalLayer, offscreen textures, VistionKit immersive spaces etc. 25 | * ~~Built-in offscreen renderer~~ (Working but API can be fixed) 26 | * Built-in support for ~~MTKView~~ and CAMetalLayer 27 | * VisionKit 'CPSceneSessionRoleImmersiveSpaceApplication' output 28 | * (The more outputs the less fragile the APO will be) 29 | * Simpler API for just getting shit on-screen 30 | * Create a simple render API that takes a single pipeline 31 | * RenderEnvironment variables modelled after SwiftUI.Environment for safety 32 | * Use Function-builder for pipelines/stages etc 33 | * Cleaner render model that more closely matches Metal. 34 | * Built-in support for RenderGraph editor (use NodeEditor) 35 | * Work with multi-sample, ray tracing 36 | * Figure out how to #include metal headers across packages (likely not possible). 37 | * Get more helper code of RenderKit and into MetalSupport etc 38 | * Use Spatial framework 39 | * Use the shape code from Spatial to bring in a `shape3d` type that can export MTKMeshes* Simple SwiftUI Canvas style line drawing mode. 40 | * Some of the rotation code here may be useful - what else is new in Spatial? 41 | * Bring over my Projection package (3d vector graphics) 42 | * Take advantage of Swift macros (macro to encode struct into a buffer compatible with SwiftUI) 43 | * Sort out the various projection APIs 44 | 45 | ## Links 46 | 47 | * 48 | -------------------------------------------------------------------------------- /Sources/RenderKit/PackedFloat.swift: -------------------------------------------------------------------------------- 1 | import simd 2 | import RenderKitShaders 3 | 4 | public extension PackedFloat3 { 5 | init(_ value: SIMD3) { 6 | self = .init(x: value.x, y: value.y, z: value.z) 7 | } 8 | } 9 | 10 | extension PackedFloat3: Equatable { 11 | public static func == (lhs: PackedFloat3, rhs: PackedFloat3) -> Bool { 12 | SIMD3(lhs) == SIMD3(rhs) 13 | } 14 | } 15 | 16 | extension PackedFloat3: Hashable { 17 | public func hash(into hasher: inout Hasher) { 18 | SIMD3(self).hash(into: &hasher) 19 | } 20 | } 21 | 22 | extension PackedFloat3: @unchecked Sendable { 23 | } 24 | 25 | extension PackedFloat3: ExpressibleByArrayLiteral { 26 | public init(arrayLiteral elements: Float...) { 27 | self = .init(x: elements[0], y: elements[1], z: elements[2]) 28 | } 29 | } 30 | 31 | public extension SIMD3 where Scalar == Float { 32 | init(_ packed: PackedFloat3) { 33 | self = .init(x: packed.x, y: packed.y, z: packed.z) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/RenderKit/RenderKitShaders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Jonathan Wight on 6/30/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Bundle { 11 | static let shadersBundle: Bundle = { 12 | // Step 1. Find the bundle as a child of main bundle. 13 | if let shadersBundleURL = Bundle.main.url(forResource: "RenderKit_RenderKitShaders", withExtension: "bundle"), let bundle = Bundle(url: shadersBundleURL) { 14 | return bundle 15 | } 16 | // Step 2. Find the bundle as peer to the current `Bundle.module` 17 | if let bundle = Bundle(url: Bundle.module.bundleURL.deletingLastPathComponent().appendingPathComponent("RenderKit_RenderKitShaders.bundle")) { 18 | return bundle 19 | } 20 | // Fail. 21 | fatalError("Could not find shaders bundle") 22 | }() 23 | } 24 | -------------------------------------------------------------------------------- /Sources/RenderKit/RenderPass.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Metal 3 | 4 | public protocol MetalConfiguration { 5 | var colorPixelFormat: MTLPixelFormat { get set } 6 | var clearColor: MTLClearColor { get set } 7 | var depthStencilPixelFormat: MTLPixelFormat { get set } 8 | var depthStencilStorageMode: MTLStorageMode { get set } 9 | var clearDepth: Double { get set } 10 | } 11 | 12 | // MARK: - 13 | 14 | public protocol RenderPass: AnyObject { 15 | func setup (device: MTLDevice, configuration: inout Configuration) throws 16 | func drawableSizeWillChange(device: MTLDevice, size: CGSize) throws 17 | func draw (device: MTLDevice, size: CGSize, renderPassDescriptor: MTLRenderPassDescriptor, commandBuffer: MTLCommandBuffer) throws 18 | } 19 | 20 | public extension RenderPass { 21 | func drawableSizeWillChange(device: MTLDevice, size: CGSize) throws { 22 | } 23 | } 24 | 25 | // MARK: - 26 | 27 | // TODO: Combine jobs and passes 28 | 29 | public protocol RenderJob: AnyObject { 30 | func setup(device: MTLDevice, configuration: inout Configuration) throws 31 | func drawableSizeWillChange(device: MTLDevice, size: CGSize) throws 32 | func encode(on encoder: MTLRenderCommandEncoder, size: CGSize) throws // TODO: Add configuration just to be consistent? Or remove from renderpass. 33 | } 34 | 35 | public extension RenderJob { 36 | func drawableSizeWillChange(device: MTLDevice, size: CGSize) throws { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/RenderKit/RendererView.swift: -------------------------------------------------------------------------------- 1 | #if !os(visionOS) 2 | import SwiftUI 3 | import MetalSupport 4 | 5 | public struct RendererView : View where T: RenderPass { 6 | // TODO: Does this _really_ need to be a Binding? 7 | @Binding 8 | var renderPass: T 9 | 10 | @State 11 | var commandQueue: MTLCommandQueue? 12 | 13 | @Environment(\.metalDevice) 14 | var device 15 | 16 | public init(renderPass: Binding) { 17 | self._renderPass = renderPass 18 | } 19 | 20 | public var body: some View { 21 | MetalView { device, configuration in 22 | configuration.preferredFramesPerSecond = 120 23 | configuration.colorPixelFormat = .bgra8Unorm_srgb 24 | configuration.depthStencilPixelFormat = .depth16Unorm 25 | configuration.depthStencilStorageMode = .memoryless 26 | try renderPass.setup(device: device, configuration: &configuration) 27 | } drawableSizeWillChange: { device, _, size in 28 | try renderPass.drawableSizeWillChange(device: device, size: size) 29 | } draw: { device, _, size, currentDrawable, renderPassDescriptor in 30 | guard let commandQueue else { 31 | fatalError("Draw called before command queue set up. This should be impossible.") 32 | } 33 | try commandQueue.withCommandBuffer(drawable: currentDrawable, block: { commandBuffer in 34 | commandBuffer.label = "RendererView-CommandBuffer" 35 | try renderPass.draw(device: device, size: size, renderPassDescriptor: renderPassDescriptor, commandBuffer: commandBuffer) 36 | }) 37 | } 38 | .onAppear { 39 | if commandQueue == nil { 40 | commandQueue = device.makeCommandQueue() 41 | } 42 | } 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Sources/RenderKit/SimpleVertex.swift: -------------------------------------------------------------------------------- 1 | import RenderKitShaders 2 | import simd 3 | 4 | public extension SimpleVertex { 5 | var position: SIMD3 { 6 | get { 7 | return SIMD3(packedPosition) 8 | } 9 | set { 10 | packedPosition = PackedFloat3(newValue) 11 | } 12 | } 13 | 14 | var normal: SIMD3 { 15 | get { 16 | return SIMD3(packedNormal) 17 | } 18 | set { 19 | packedNormal = PackedFloat3(newValue) 20 | } 21 | } 22 | 23 | init(position: SIMD3, normal: SIMD3, textureCoordinate: SIMD2) { 24 | self = .init(packedPosition: PackedFloat3(position), packedNormal: PackedFloat3(normal), textureCoordinate: textureCoordinate) 25 | } 26 | } 27 | 28 | extension SimpleVertex: @unchecked Sendable { 29 | } 30 | 31 | extension SimpleVertex: Equatable { 32 | public static func == (lhs: SimpleVertex, rhs: SimpleVertex) -> Bool { 33 | lhs.packedPosition == rhs.packedPosition && lhs.packedNormal == rhs.packedNormal && lhs.textureCoordinate == rhs.textureCoordinate 34 | } 35 | } 36 | 37 | extension SimpleVertex: Hashable { 38 | public func hash(into hasher: inout Hasher) { 39 | packedPosition.hash(into: &hasher) 40 | packedNormal.hash(into: &hasher) 41 | textureCoordinate.hash(into: &hasher) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/RenderKit/Support/Functions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // TODO: move 4 | 5 | // https://docs.gl/sl4/step 6 | // https://www.youtube.com/watch?v=YJB1QnEmlTs 7 | 8 | func step (_ x: V, _ a: V) -> V { 9 | x < a ? 0.0 : 1.0 10 | } 11 | 12 | func clamp(_ x: V, _ a: V, _ b: V) -> V { 13 | if x < a { 14 | return a 15 | } 16 | if x > b { 17 | return b 18 | } 19 | return x 20 | } 21 | 22 | func saturate(_ x: V) -> V { 23 | return clamp(x, 0.0, 1.0) 24 | } 25 | 26 | func smoothstep(_ a: V, _ b: V, _ x: V) -> V { 27 | let y = saturate((x - a) / (b - a)) 28 | return y * y * (3.0 - 2.0 * y) 29 | } 30 | 31 | // from https://graphtoy.com 32 | //function clamp(x,a,b) { if( xb ) return b; return x; } 33 | //function saturate(x) { return clamp(x,0.0,1.0); } 34 | //function remap(a,b,x,c,d) { if( xb ) return d; let y=(x-a)/(b-a); return c + (d-c)*y; } 35 | //function smoothstep(a,b,x) { let y = saturate((x-a)/(b-a)); return y*y*(3.0-2.0*y); } 36 | //function ssign(x) { return (x>=0.0)?1.0:-1.0; } 37 | //function radians(degrees) { return degrees*Math.PI/180.0; } 38 | //function degrees(radians) { return radians*180.0/Math.PI; } 39 | //function inversesqrt(x) { return 1.0/Math.sqrt(x); } 40 | //function rsqrt(x) { return inversesqrt(x); } 41 | //function rcbrt(x) { return 1.0/Math.cbrt(x); } 42 | //function rcp(x) { return 1.0/x; } 43 | //function fma(x,y,z) { return x*y+z; } 44 | //function step(a,x) { return (x0.0) ? x : x+1.0; if(xa)?1.0:-1.0; } 50 | //function frac(x) { return x - Math.floor(x); } 51 | //function fract(x) { return frac(x); } 52 | //function exp2(x) { return pow(2.0,x); } 53 | //function exp10(x) { return pow(10.0,x); } 54 | //function mod(x,y) { return x-y*Math.floor(x/y); } 55 | //function cellnoise(x) 56 | //{ 57 | // let n = Math.floor(x) | 0; 58 | // n = (n << 13) ^ n; n &= 0xffffffff; 59 | // let m = n; 60 | // n = n * 15731; n &= 0xffffffff; 61 | // n = n * m; n &= 0xffffffff; 62 | // n = n + 789221; n &= 0xffffffff; 63 | // n = n * m; n &= 0xffffffff; 64 | // n = n + 1376312589; n &= 0xffffffff; 65 | // n = (n>>14) & 65535; 66 | // return n/65535.0; 67 | //} 68 | //function voronoi(x) 69 | //{ 70 | // const i = Math.floor(x); 71 | // const f = x - i; 72 | // const x0 = cellnoise(i-1); const d0 = Math.abs(f-(-1+x0)); 73 | // const x1 = cellnoise(i ); const d1 = Math.abs(f-( x1)); 74 | // const x2 = cellnoise(i+1); const d2 = Math.abs(f-( 1+x2)); 75 | // let r = d0; 76 | // r = (d1 Self { 8 | nextIndexByPrefix.withLock { nextIndexByPrefix in 9 | let index = nextIndexByPrefix[prefix, default: 0] 10 | let id = LOLID2(rawValue: "\(prefix)-\(index)") 11 | nextIndexByPrefix[prefix] = index + 1 12 | return id 13 | } 14 | } 15 | 16 | internal let rawValue: String 17 | 18 | internal init(rawValue: String) { 19 | self.rawValue = rawValue 20 | } 21 | 22 | public init(prefix: String) { 23 | self = LOLID2.generate(prefix: prefix) 24 | } 25 | } 26 | 27 | extension LOLID2: CustomStringConvertible { 28 | public var description: String { 29 | return rawValue 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/RenderKit/Support/Logging.swift: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | let logger: Logger? = Logger() 4 | -------------------------------------------------------------------------------- /Sources/RenderKit/VisionOS/Assets.xcassets/ColorMap.textureset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "properties" : { 7 | "origin" : "bottom-left", 8 | "interpretation" : "non-premultiplied-colors" 9 | }, 10 | "textures" : [ 11 | { 12 | "idiom" : "universal", 13 | "filename" : "Universal.mipmapset" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /Sources/RenderKit/VisionOS/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/ColorMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwa/RenderKit/9d8ea58902e2a6e270faf7b9edfef64b62beaec5/Sources/RenderKit/VisionOS/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/ColorMap.png -------------------------------------------------------------------------------- /Sources/RenderKit/VisionOS/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "levels" : [ 7 | { 8 | "filename" : "ColorMap.png", 9 | "mipmap-level" : "base" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Sources/RenderKit/VisionOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/RenderKitScratch/CircularPairs.swift: -------------------------------------------------------------------------------- 1 | public extension Sequence { 2 | func circularPairs() -> CircularPairsSequence { 3 | CircularPairsSequence(base: self) 4 | } 5 | } 6 | 7 | public struct CircularPairsSequence { 8 | var base: Base 9 | 10 | public init(base: Base) { 11 | self.base = base 12 | } 13 | } 14 | 15 | extension CircularPairsSequence: Sequence { 16 | public func makeIterator() -> Iterator { 17 | Iterator(base: base.makeIterator()) 18 | } 19 | 20 | public struct Iterator: IteratorProtocol { 21 | var base: Base.Iterator 22 | var first: Base.Element? 23 | var previous: Base.Element? 24 | var atEnd = false 25 | 26 | public mutating func next() -> (Base.Element, Base.Element)? { 27 | switch (base.next(), first, previous, atEnd) { 28 | case (.none, .none, .none, false): 29 | return nil 30 | case (.some(let current), .none, .none, false): 31 | guard let next = base.next() else { 32 | return (current, current) 33 | } 34 | first = current 35 | previous = next 36 | return (current, next) 37 | case (.some(let current), .some, .some(let previous), false): 38 | self.previous = current 39 | return (previous, current) 40 | case (.none, .some(let first), .some(let previous), false): 41 | atEnd = true 42 | return (previous, first) 43 | case (_, _, _, true): 44 | return nil 45 | default: 46 | fatalError() 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/RenderKitScratch/HighContrastColors.swift: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/470690/how-to-automatically-generate-n-distinct-colors/4382138#4382138 2 | // P. Green-Armytage (2010): A Colour Alphabet and the Limits of Colour Coding. // Colour: Design & Creativity (5) (2010): 10, 1-23 3 | // https://aic-color.org/resources/Documents/jaic_v5_06.pdf 4 | // https://ui.adsabs.harvard.edu/abs/1989SPIE.1077..322B/abstract 5 | // https://stackoverflow.com/questions/470690/how-to-automatically-generate-n-distinct-colors/4382138#4382138 6 | 7 | import SwiftUI 8 | 9 | public let kellyColors: [(Float, Float, Float)] = [ 10 | 0xFFB300, // Vivid Yellow 11 | 0x803E75, // Strong Purple 12 | 0xFF6800, // Vivid Orange 13 | 0xA6BDD7, // Very Light Blue 14 | 0xC10020, // Vivid Red 15 | 0xCEA262, // Grayish Yellow 16 | 0x817066, // Medium Gray 17 | 0x007D34, // Vivid Green 18 | 0xF6768E, // Strong Purplish Pink 19 | 0x00538A, // Strong Blue 20 | 0xFF7A5C, // Strong Yellowish Pink 21 | 0x53377A, // Strong Violet 22 | 0xFF8E00, // Vivid Orange Yellow 23 | 0xB32851, // Strong Purplish Red 24 | 0xF4C800, // Vivid Greenish Yellow 25 | 0x7F180D, // Strong Reddish Brown 26 | 0x93AA00, // Vivid Yellowish Green 27 | 0x593315, // Deep Yellowish Brown 28 | 0xF13A13, // Vivid Reddish Orange 29 | 0x232C16, // Dark Olive Green 30 | ].map { hex in 31 | let red = hex >> 16 & 0xFF 32 | let green = hex >> 8 & 0xFF 33 | let blue = hex & 0xFF 34 | return (Float(red) / 255, Float(green) / 255, Float(blue) / 255) 35 | } 36 | 37 | public extension Color { 38 | init(rgb: (Float, Float, Float)) { 39 | self = .init(red: Double(rgb.0), green: Double(rgb.1), blue: Double(rgb.2)) 40 | } 41 | } 42 | 43 | // Green-Armytage 44 | //240,163,255 45 | //0,117,220 46 | //153,63,0 47 | //76,0,92 48 | //25,25,25 49 | //0,92,49 50 | //43,206,72 51 | //255,204,153 52 | //128,128,128 53 | //148,255,181 54 | //143,124,0 55 | //157,204,0 56 | //194,0,136 57 | //0,51,128 58 | //255,164,5 59 | //255,168,187 60 | //66,102,0 61 | //255,0,16 62 | //94,241,242 63 | //0,153,143 64 | //224,255,102 65 | //116,10,255 66 | //153,0,0 67 | //255,255,128 68 | //255,255,0 69 | //255,80,5 70 | -------------------------------------------------------------------------------- /Sources/RenderKitScratch/PLYEncoder.swift: -------------------------------------------------------------------------------- 1 | public struct PlyEncoder { 2 | public typealias Output = String 3 | 4 | public init() { 5 | } 6 | 7 | public func encodeHeader(to output: inout Output) { 8 | print("ply", to: &output) 9 | } 10 | 11 | public func encodeVersion(to output: inout Output) { 12 | print("format ascii 1.0", to: &output) 13 | } 14 | 15 | public func encodeComment(_ comment: String, to output: inout Output) { 16 | print("comment \(comment)", to: &output) 17 | } 18 | 19 | public enum NumericalType: String { 20 | case char 21 | case uchar 22 | case short 23 | case ushort 24 | case int 25 | case uint 26 | case float 27 | case double 28 | } 29 | 30 | public enum Value { 31 | case char(Int8) 32 | case uchar(UInt8) 33 | case short(Int16) 34 | case ushort(UInt16) 35 | case int(Int32) 36 | case uint(UInt32) 37 | case float(Float) 38 | case double(Double) 39 | } 40 | 41 | public enum Kind { 42 | case numerical(NumericalType) 43 | case list(count: NumericalType, element: NumericalType) 44 | } 45 | 46 | // element vertex 12 47 | // property float x 48 | // property float y 49 | // property float z 50 | 51 | public func encodeElementDefinition(name: String, count: Int, properties: [(Kind, String)], to output: inout Output) { 52 | print("element \(name) \(count)", to: &output) 53 | for (kind, name) in properties { 54 | switch kind { 55 | case .numerical(let numericType): 56 | print("property \(numericType.rawValue) \(name)", to: &output) 57 | case .list(let count, let element): 58 | print("property list \(count.rawValue) \(element.rawValue) \(name)", to: &output) 59 | } 60 | } 61 | } 62 | 63 | public func encodeEndHeader(to output: inout Output) { 64 | print("end_header", to: &output) 65 | } 66 | 67 | public func encodeElement(_ values: [Value], to output: inout Output) { 68 | print(values.map { $0.description }.joined(separator: " "), to: &output) 69 | } 70 | 71 | public func encodeListElement(_ values: [Value], to output: inout Output) { 72 | print("\(values.count) \(values.map { $0.description }.joined(separator: " "))", to: &output) 73 | } 74 | } 75 | 76 | public extension PlyEncoder.Kind { 77 | static let char = Self.numerical(.char) 78 | static let uchar = Self.numerical(.uchar) 79 | static let short = Self.numerical(.short) 80 | static let ushort = Self.numerical(.ushort) 81 | static let int = Self.numerical(.int) 82 | static let uint = Self.numerical(.uint) 83 | static let float = Self.numerical(.float) 84 | static let double = Self.numerical(.double) 85 | } 86 | 87 | extension PlyEncoder.Value: CustomStringConvertible { 88 | public var description: String { 89 | switch self { 90 | case .char(let value): 91 | return "\(value)" 92 | case .uchar(let value): 93 | return "\(value)" 94 | case .short(let value): 95 | return "\(value)" 96 | case .ushort(let value): 97 | return "\(value)" 98 | case .int(let value): 99 | return "\(value)" 100 | case .uint(let value): 101 | return "\(value)" 102 | case .float(let value): 103 | return "\(value)" 104 | case .double(let value): 105 | return "\(value)" 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/RenderKitScratch/Scratch.swift: -------------------------------------------------------------------------------- 1 | import RenderKit 2 | import RenderKitShaders 3 | import simd 4 | import SIMDSupport 5 | 6 | extension SIMD3 where Scalar == Float { 7 | func dot(_ other: Self) -> Float { 8 | simd_dot(self, other) 9 | } 10 | } 11 | 12 | extension SimpleVertex { 13 | init(position: SIMD3, normal: SIMD3) { 14 | self.init(position: position, normal: normal, textureCoordinate: .zero) 15 | } 16 | } 17 | 18 | extension Plane: CustomStringConvertible { 19 | public var description: String { 20 | return "Plane(normal: \(normal.x), \(normal.y), \(normal.z), w: \(w))" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/RenderKitScratch/Shapes+Intersections.swift: -------------------------------------------------------------------------------- 1 | import simd 2 | 3 | public extension LineSegment3D { 4 | func intersection(with other: LineSegment3D) -> LineSegment3D? { 5 | // https://stackoverflow.com/a/565282/273118 6 | let p = start 7 | let q = other.start 8 | let r = direction 9 | let s = other.direction 10 | 11 | let rCrossS = simd.cross(r, s) 12 | let qMinusP = q - p 13 | let qMinusPCrossR = simd.cross(qMinusP, r) 14 | 15 | let t = simd.dot(qMinusPCrossR, rCrossS) / simd.dot(rCrossS, rCrossS) 16 | let u = simd.dot(qMinusPCrossR, rCrossS) / simd.dot(rCrossS, rCrossS) 17 | 18 | if t >= 0, t <= 1, u >= 0, u <= 1 { 19 | return LineSegment3D(start: start + t * direction, end: start + t * direction) 20 | } 21 | else { 22 | return nil 23 | } 24 | } 25 | } 26 | 27 | public extension Line3D { 28 | enum LineSphereIntersection { 29 | case once(SIMD3) 30 | case twice(SIMD3, SIMD3) 31 | } 32 | 33 | func intersects(sphere: Sphere) -> LineSphereIntersection? { 34 | fatalError() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/RenderKitScratch/Shapes+ShapeScript.swift: -------------------------------------------------------------------------------- 1 | public protocol ShapeScriptEncodable { 2 | func encodeToShapeScript() throws -> String 3 | } 4 | 5 | // https://shapescript.info/mac/ 6 | extension Sphere: ShapeScriptEncodable { 7 | public func encodeToShapeScript() throws -> String { 8 | """ 9 | sphere { 10 | size \(radius * 2) 11 | position \(center.x) \(center.y) \(center.z) 12 | detail 36 13 | } 14 | """ 15 | } 16 | } 17 | 18 | extension LineSegment3D: ShapeScriptEncodable { 19 | public func encodeToShapeScript() throws -> String { 20 | """ 21 | path { 22 | point \(start.x) \(start.y) \(start.z) 23 | point \(end.x) \(end.y) \(end.z) 24 | } 25 | """ 26 | } 27 | } 28 | 29 | extension Line3D: ShapeScriptEncodable { 30 | public func encodeToShapeScript() throws -> String { 31 | let segment = LineSegment3D(start: point + -direction * 1000, end: point + direction * 1000) 32 | return try segment.encodeToShapeScript() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/CheckerBoardCompute.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "include/Shaders.h" 4 | 5 | using namespace metal; 6 | 7 | kernel void checkerboard( 8 | uint2 gid [[thread_position_in_grid]], 9 | uint2 grid_size [[grid_size]], 10 | texture2d outputTexture [[texture(CheckerBoardComputeBindings_OutputTexture)]], 11 | constant float2 &size [[buffer(CheckerBoardComputeBindings_SizeBuffer)]]) 12 | { 13 | float2 t = fmod(float2(gid), size) / size; 14 | 15 | float v1 = t.x >= 0.5 ? 1.0 : 0; 16 | float v2 = t.y >= 0.5 ? v1 : 1 - v1; 17 | 18 | outputTexture.write(float4(v2, 0, v2, 1), gid); 19 | } 20 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/GPULifeKernel.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "include/Shaders.h" 5 | 6 | using namespace metal; 7 | 8 | bool rules(uint count, bool alive) { 9 | if (alive == true && (count == 2 || count == 3)) { 10 | return true; 11 | } 12 | else if (alive == false && count == 3) { 13 | return true; 14 | } 15 | else if (alive == true) { 16 | return false; 17 | } 18 | else { 19 | return false; 20 | } 21 | } 22 | 23 | kernel void gpuLifeKernelWrap( 24 | uint2 gid [[thread_position_in_grid]], 25 | texture2d inputTexture [[texture(GPULifeKernelBindings_InputTexture)]], 26 | texture2d outputTexture [[texture(GPULifeKernelBindings_OutputTexture)]]) { 27 | 28 | const int2 inputTextureSize = int2(inputTexture.get_width(), inputTexture.get_height()); 29 | if (int(gid.x) >= inputTextureSize.x || int(gid.y) >= inputTextureSize.y) { 30 | return; 31 | } 32 | 33 | const int2 positions[] = { 34 | int2(-1, -1), 35 | int2( 0, -1), 36 | int2(+1, -1), 37 | int2(-1, 0), 38 | int2(+1, 0), 39 | int2(-1, +1), 40 | int2( 0, +1), 41 | int2(+1, +1), 42 | }; 43 | uint count = 0; 44 | 45 | for (int N = 0; N != 8; ++N) { 46 | int2 position = int2(gid) + positions[N]; 47 | position.x = (position.x + inputTextureSize.x) % inputTextureSize.x; 48 | position.y = (position.y + inputTextureSize.y) % inputTextureSize.y; 49 | count += inputTexture.read(uint2(position)).r ? 1 : 0; 50 | } 51 | 52 | const bool alive = inputTexture.read(gid).r != 0; 53 | 54 | outputTexture.write(rules(count, alive), gid); 55 | } 56 | 57 | kernel void gpuLifeKernalNoWrap( 58 | uint2 gid [[thread_position_in_grid]], 59 | texture2d inputTexture [[texture(GPULifeKernelBindings_InputTexture)]], 60 | texture2d outputTexture [[texture(GPULifeKernelBindings_OutputTexture)]]) { 61 | 62 | const int2 sgid = int2(gid); 63 | const int2 inputTextureSize = int2(inputTexture.get_width(), inputTexture.get_height()); 64 | if (sgid.x >= inputTextureSize.x || sgid.y >= inputTextureSize.y) { 65 | return; 66 | } 67 | 68 | uint count = 0; 69 | 70 | const int2 positions[] = { 71 | int2(-1, -1), 72 | int2( 0, -1), 73 | int2(+1, -1), 74 | int2(-1, 0), 75 | int2(+1, 0), 76 | int2(-1, +1), 77 | int2( 0, +1), 78 | int2(+1, +1), 79 | }; 80 | 81 | 82 | for (int N = 0; N != 8; ++N) { 83 | int2 position = sgid + positions[N]; 84 | 85 | if (position.x < 0 || position.x >= inputTextureSize.x) { 86 | continue; 87 | } 88 | else if (position.y < 0 || position.y >= inputTextureSize.y) { 89 | continue; 90 | } 91 | count += inputTexture.read(uint2(position)).r ? 1 : 0; 92 | } 93 | 94 | const bool alive = inputTexture.read(gid).r != 0; 95 | 96 | outputTexture.write(rules(count, alive), gid); 97 | } 98 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/ParticleShaders.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "include/Shaders.h" 3 | #include "include/ParticleShaders.h" 4 | #include "include/Random.h" 5 | 6 | using namespace metal; 7 | 8 | float3 apply_forces(float3 vel, float drag, float mass) 9 | { 10 | float3 grav_acc = float3{0.0, -9.81, 0 }; // 9.81m/s^2 down in the Z-axis 11 | float3 drag_force = 0.5 * drag * (vel * abs(vel)); // D = 0.5 * (rho * C * Area * vel^2) 12 | float3 drag_acc = drag_force / mass; // a = F/m 13 | return grav_acc - drag_acc; 14 | } 15 | 16 | // MARK: - 17 | 18 | [[kernel]] 19 | void particleUpdate( 20 | uint gid [[thread_position_in_grid]], 21 | device Particle *particles[[buffer(ParticleShadersBindings_ParticlesBuffer)]], 22 | constant ParticlesEnvironment &environment [[buffer(ParticleShadersBindings_EnvironmentBuffer)]] 23 | ) 24 | { 25 | auto particle = particles[gid]; 26 | if (particle.age >= particle.lifetime || particle.position.y < 0) { 27 | particle.age = 0; 28 | particle.position = float3(0, 0, 0); 29 | particle.oldPosition = float3(0, 0, 0); 30 | particle.acceleration = (rand1dTo3d(gid) + float3(-0.5, 0, -0.5)) * float3(200, 2000, 200); 31 | } 32 | const auto temp = particle.position; 33 | const float timestep = environment.timestep; 34 | particle.position += particle.position - particle.oldPosition + particle.acceleration * timestep * timestep; 35 | particle.oldPosition = temp; 36 | particle.age += timestep; 37 | particle.acceleration = apply_forces(particle.position - particle.oldPosition, 0.1, 1); 38 | particles[gid] = particle; 39 | } 40 | 41 | // MARK: - 42 | 43 | struct Fragment 44 | { 45 | float4 position [[position]]; 46 | float2 textureCoordinate; 47 | uint particle_id; 48 | }; 49 | 50 | [[vertex]] 51 | Fragment classicParticleVertexShader( 52 | Vertex in [[stage_in]], 53 | constant Transforms &transforms [[buffer(CommonBindings_TransformsBuffer)]], 54 | device const Particle *particles[[buffer(ParticleShadersBindings_ParticlesBuffer)]], 55 | uint iid [[instance_id]]) 56 | { 57 | const auto particle = particles[iid]; 58 | Fragment out; 59 | out.position = transforms.projection * transforms.modelView * float4(in.position + particle.position, 1.0); 60 | out.textureCoordinate = in.textureCoordinate; 61 | out.particle_id = iid; 62 | return out; 63 | } 64 | 65 | [[fragment]] 66 | float4 classicParticleFragmentShader( 67 | Fragment in [[stage_in]], 68 | device const Particle *particles[[buffer(ParticleShadersBindings_ParticlesBuffer)]] 69 | ) 70 | { 71 | const auto particle = particles[in.particle_id]; 72 | if (particle.age >= particle.lifetime) { 73 | discard_fragment(); 74 | } 75 | return float4(1, 0, 1, 1) * 1 - (particle.age / particle.lifetime); 76 | } 77 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/ShaderToy.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "include/Shaders.h" 4 | 5 | using namespace metal; 6 | 7 | struct Fragment 8 | { 9 | float4 position [[position]]; 10 | float2 textureCoordinate; 11 | }; 12 | 13 | vertex Fragment ShaderToy_VertexShader( 14 | Vertex in [[stage_in]], 15 | constant Transforms &transforms [[buffer(CommonBindings_TransformsBuffer)]]) 16 | { 17 | Fragment out; 18 | out.position = transforms.projection * transforms.modelView * float4(in.position, 1.0); 19 | out.position.z -= 0.001; // TODO: Parameterize 20 | out.textureCoordinate = in.textureCoordinate; 21 | return out; 22 | } 23 | 24 | float plot(float2 st) { 25 | return smoothstep(0.02, 0.0, abs(st.y - st.x)); 26 | } 27 | 28 | fragment float4 ShaderToy_FragmentShader( 29 | Fragment in [[stage_in]], 30 | constant FrameState &frameState [[buffer(CommonBindings_FrameStateBuffer)]]) 31 | { 32 | float2 st = in.textureCoordinate; 33 | float y = st.x; 34 | float3 color = float3(y); 35 | float pct = plot(st); 36 | color = (1.0-pct)*color+pct*float3(0.0,1.0,0.0); 37 | return float4(color,1.0); 38 | 39 | //return float4(sin(frameState.time * 4) / 2 + 0.5, 0, 0, 1); 40 | //return float4(in.textureCoordinate.x, 0, 0, 1); 41 | } 42 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/Support.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "include/Common.h" 4 | #include "include/Support.h" 5 | 6 | using namespace metal; 7 | 8 | float3 GammaCorrect(float3 color, float screenGamma) { 9 | // apply gamma correction (assume ambientColor, diffuseColor and specularColor have been linearized, i.e. have no gamma correction in them) 10 | const float3 colorGammaCorrected = pow(color, float3(1.0 / screenGamma)); 11 | // use the gamma corrected color in the fragment 12 | return colorGammaCorrected; 13 | } 14 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/Bindings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | typedef NS_ENUM(NSInteger, CommonBindings) { 6 | CommonBindings_VerticesBuffer = 0, 7 | CommonBindings_FrameStateBuffer = 1, 8 | CommonBindings_TransformsBuffer = 2, 9 | }; 10 | 11 | typedef NS_ENUM(NSInteger, SamplerBindings) { 12 | UnlitBaseColorSamplerIndex = 3 13 | }; 14 | 15 | typedef NS_ENUM(NSInteger, BlinnPhongBindings) { 16 | BlinnPhongBindings_LightingModelArgumentBuffer = 4, 17 | BlinnPhongBindings_MaterialArgumentBuffer = 5, 18 | BlinnPhongBindings_BlinnPhongModeConstant = 6 19 | }; 20 | 21 | typedef NS_ENUM(NSInteger, UnlitShaderBindings) { 22 | UnlitShaderBindings_BaseColorTexture = 7, 23 | UnlitShaderBindings_BaseColorSampler = 8, 24 | UnlitShaderBindings_OffsetsBuffer = 9, 25 | }; 26 | 27 | typedef NS_ENUM(NSInteger, CheckerBoardComputeBindings) { 28 | CheckerBoardComputeBindings_OutputTexture = 10, 29 | CheckerBoardComputeBindings_SizeBuffer = 11, 30 | }; 31 | 32 | typedef NS_ENUM(NSInteger, VoronoiNoiseComputeBindings) { 33 | VoronoiNoiseComputeBindings_OutputTexture = 12, 34 | VoronoiNoiseComputeBindings_OffsetBuffer = 13, 35 | VoronoiNoiseComputeBindings_SizeBuffer = 14, 36 | VoronoiNoiseComputeBindings_ModeBuffer = 15, 37 | }; 38 | 39 | 40 | typedef NS_ENUM(NSInteger, SimplexNoise2DBindings) { 41 | SimplexNoise2DBindings_OutputTexture = 16, 42 | }; 43 | 44 | 45 | typedef NS_ENUM(NSInteger, GPULifeKernelBindings) { 46 | GPULifeKernelBindings_InputTexture = 17, 47 | GPULifeKernelBindings_OutputTexture = 18, 48 | }; 49 | 50 | 51 | typedef NS_ENUM(NSInteger, VoxelsBindings) { 52 | VoxelsBindings_VoxelsBuffer = 19, 53 | VoxelsBindings_ColorPaletteTexture = 20, 54 | VoxelsBindings_OutputTexture = 21, 55 | VoxelsBindings_VoxelSizeBuffer = 22, 56 | VoxelsBindings_VerticesBuffer = 23, 57 | VoxelsBindings_IndicesBuffer = 24, 58 | VoxelsBindings_BlinnPhongLightingModelArgumentBuffer = BlinnPhongBindings_LightingModelArgumentBuffer, 59 | }; 60 | 61 | typedef NS_ENUM(NSInteger, DebugShaderBindings) { 62 | DebugShaderBindings_ModeBuffer = 25, 63 | }; 64 | 65 | typedef NS_ENUM(NSInteger, ParticleShadersBindings) { 66 | ParticleShadersBindings_EnvironmentBuffer = 26, 67 | ParticleShadersBindings_ParticlesBuffer = 27, 68 | }; 69 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/BlinnPhongShaders.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __METAL_VERSION__ 4 | #include 5 | using namespace metal; 6 | #endif 7 | 8 | #import 9 | 10 | #import "Common.h" 11 | 12 | struct BlinnPhongLight { 13 | simd_float3 lightPosition; 14 | simd_float3 lightColor; 15 | float lightPower; 16 | }; 17 | 18 | #ifdef __METAL_VERSION__ 19 | struct BlinnPhongMaterialArgumentBuffer { 20 | texture2d ambientTexture; 21 | sampler ambientSampler; 22 | texture2d diffuseTexture; 23 | sampler diffuseSampler; 24 | texture2d specularTexture; 25 | sampler specularSampler; 26 | float shininess; 27 | }; 28 | 29 | struct BlinnPhongLightingModelArgumentBuffer { 30 | float screenGamma; // TODO: Move 31 | int lightCount; 32 | simd_float3 ambientLightColor; // TODO 33 | device BlinnPhongLight *lights; 34 | }; 35 | 36 | float3 CalculateBlinnPhong(float3 modelPosition, 37 | float3 interpolatedNormal, 38 | constant BlinnPhongLightingModelArgumentBuffer &lightingModel, 39 | float shininess, 40 | float3 ambientColor, 41 | float3 diffuseColor, 42 | float3 specularColor 43 | ); 44 | #endif 45 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/Common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __METAL_VERSION__ 4 | #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type 5 | #define NSInteger metal::int32_t 6 | #include 7 | #else 8 | #import 9 | #endif 10 | 11 | #import 12 | 13 | struct FrameState { 14 | // simd_float4x4 projection; // camera space -> clip space 15 | // simd_float2 drawableSize; 16 | float time; 17 | // float lastTime; 18 | long frame; 19 | float desiredFPS; 20 | // apply gamma correction (assume ambientColor, diffuseColor and specularColor have been linearized, i.e. have no gamma correction in them) 21 | // Assume the monitor is calibrated to the sRGB color space 22 | float screenGamma; // 2.2 23 | }; 24 | 25 | 26 | struct Transforms { // Rename to ModelState? 27 | simd_float4x4 modelView; // model space -> camera space 28 | simd_float3x3 modelNormal; // model space - used for non-uniform scaled normal transformation. See https://www.youtube.com/watch?v=esC1HnyD9Bk&list=PLplnkTzzqsZS3R5DjmCQsqupu43oS9CFN 29 | simd_float4x4 projection; // Move to WorldState? 30 | }; 31 | 32 | struct Vertex { 33 | #ifdef __METAL_VERSION__ 34 | float3 position [[attribute(0)]]; 35 | float3 normal [[attribute(1)]]; 36 | float2 textureCoordinate [[attribute(2)]]; 37 | #else 38 | simd_float3 position; 39 | simd_float3 normal; 40 | simd_float2 textureCoordinate; 41 | // tangents etc 42 | #endif 43 | }; 44 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/GLSLCompat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __METAL_VERSION__ 4 | #include 5 | #endif 6 | 7 | using namespace metal; 8 | 9 | // GLSL Compat 10 | 11 | typedef float2 vec2; 12 | typedef float3 vec3; 13 | typedef float4 vec4; 14 | 15 | inline float2 mod(float2 lhs, float rhs) { 16 | return fmod(lhs, rhs); 17 | } 18 | 19 | inline float3 mod(float3 lhs, float rhs) { 20 | return fmod(lhs, rhs); 21 | } 22 | 23 | inline float3 mod(float3 lhs, float3 rhs) { 24 | return fmod(lhs, rhs); 25 | } 26 | 27 | inline float3 frac(float3 v) { 28 | return fract(v); 29 | } 30 | 31 | inline float2 frac(float2 v) { 32 | return fract(v); 33 | } 34 | 35 | inline float frac(float v) { 36 | return fract(v); 37 | } 38 | 39 | 40 | // MARK: - 41 | 42 | //inline float random (vec2 st) { 43 | // return fract(sin(dot(st.xy, 44 | // vec2(12.9898,78.233)))* 45 | // 43758.5453123); 46 | //} 47 | 48 | //// MARK: - 49 | // 50 | //inline float rand2dTo1d(vec2 input) { 51 | // return random(input); 52 | //} 53 | // 54 | ////inline float rand3dTo1d(vec3 input) { 55 | //// return random(input); 56 | ////} 57 | // 58 | //inline vec2 rand2dTo2d(vec2 input) { 59 | // auto x = random(vec2(input.x * 2, input.y)); 60 | // auto y = random(vec2(input.x * 2 + 1, input.y)); 61 | // return vec2(x, y); 62 | //} 63 | // 64 | //inline float3 rand1dTo3d(float input) { 65 | // return float3( 66 | // random(input * 3), 67 | // random(input * 3 + 1), 68 | // random(input * 3 + 2) 69 | // ); 70 | //} 71 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/ParticleShaders.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #import 4 | 5 | struct Particle { 6 | simd_float3 position; 7 | simd_float3 oldPosition; 8 | simd_float3 acceleration; 9 | float age; 10 | float lifetime; 11 | }; 12 | 13 | struct ParticlesEnvironment { 14 | simd_float3 gravity; 15 | float timestep; 16 | }; 17 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/Random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GLSLCompat.h" 4 | 5 | // https://www.ronja-tutorials.com/post/024-white-noise/ 6 | 7 | //get a scalar random value from a 3d value 8 | inline float rand3dTo1d(float3 value, float3 dotDir = float3(12.9898, 78.233, 37.719)){ 9 | //make value smaller to avoid artefacts 10 | float3 smallValue = sin(value); 11 | //get scalar value from 3d vector 12 | float random = dot(smallValue, dotDir); 13 | //make value more random by making it bigger and then taking the factional part 14 | random = frac(sin(random) * 143758.5453); 15 | return random; 16 | } 17 | 18 | inline float rand2dTo1d(float2 value, float2 dotDir = float2(12.9898, 78.233)){ 19 | float2 smallValue = sin(value); 20 | float random = dot(smallValue, dotDir); 21 | random = frac(sin(random) * 143758.5453); 22 | return random; 23 | } 24 | 25 | inline float rand1dTo1d(float3 value, float mutator = 0.546){ 26 | float3 random = frac(sin(value + mutator) * 143758.5453); 27 | return random.x; 28 | } 29 | 30 | //to 2d functions 31 | 32 | inline float2 rand3dTo2d(float3 value){ 33 | return float2( 34 | rand3dTo1d(value, float3(12.989, 78.233, 37.719)), 35 | rand3dTo1d(value, float3(39.346, 11.135, 83.155)) 36 | ); 37 | } 38 | 39 | inline float2 rand2dTo2d(float2 value){ 40 | return float2( 41 | rand2dTo1d(value, float2(12.989, 78.233)), 42 | rand2dTo1d(value, float2(39.346, 11.135)) 43 | ); 44 | } 45 | 46 | inline float2 rand1dTo2d(float value){ 47 | return float2( 48 | rand2dTo1d(value, 3.9812), 49 | rand2dTo1d(value, 7.1536) 50 | ); 51 | } 52 | 53 | //to 3d functions 54 | 55 | inline float3 rand3dTo3d(float3 value){ 56 | return float3( 57 | rand3dTo1d(value, float3(12.989, 78.233, 37.719)), 58 | rand3dTo1d(value, float3(39.346, 11.135, 83.155)), 59 | rand3dTo1d(value, float3(73.156, 52.235, 09.151)) 60 | ); 61 | } 62 | 63 | inline float3 rand2dTo3d(float2 value){ 64 | return float3( 65 | rand2dTo1d(value, float2(12.989, 78.233)), 66 | rand2dTo1d(value, float2(39.346, 11.135)), 67 | rand2dTo1d(value, float2(73.156, 52.235)) 68 | ); 69 | } 70 | 71 | inline float3 rand1dTo3d(float value){ 72 | return float3( 73 | rand1dTo1d(value, 3.9812), 74 | rand1dTo1d(value, 7.1536), 75 | rand1dTo1d(value, 5.7241) 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/Shaders.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This is the main umbrella header for shaders module 4 | 5 | #include "Common.h" 6 | #include "BlinnPhongShaders.h" 7 | #include "Voxels.h" 8 | #include "Bindings.h" 9 | #include "Support.h" 10 | #include "ParticleShaders.h" 11 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/Support.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #import 4 | 5 | #ifdef __METAL_VERSION__ 6 | 7 | #include 8 | 9 | using namespace metal; 10 | 11 | extern float3 GammaCorrect(float3 color, float screenGamma); 12 | #endif 13 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Classic/include/Voxels.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __METAL_VERSION__ 4 | #include 5 | #else 6 | #import 7 | #endif 8 | 9 | #import 10 | 11 | struct VoxelVertex { 12 | #ifdef __METAL_VERSION__ 13 | half3 position [[attribute(0)]]; // 6 bytes 14 | half3 normal [[attribute(1)]]; // 6 bytes 15 | half2 textureCoordinate [[attribute(2)]]; // 4 bytes 16 | ushort colorIndex [[attribute(3)]]; // 2 bytes 17 | ushort unused [[attribute(4)]]; 18 | #else 19 | // simd_half3 position; 20 | // simd_half3 normal; 21 | // simd_half2 textureCoordinate; 22 | // unsigned short colorIndex 23 | // unsigned short unused; 24 | // tangents etc 25 | #endif 26 | }; 27 | 28 | // PackedVoxelVertex and VoxelVertex have to have the same structure 29 | #ifdef __METAL_VERSION__ 30 | struct PackedVoxelVertex { 31 | packed_half3 position; 32 | packed_half3 normal; 33 | packed_half2 textureCoordinate; 34 | ushort colorIndex; 35 | ushort unused; 36 | }; 37 | 38 | struct MagicaVoxel { 39 | uchar3 position; 40 | uchar color; 41 | }; 42 | #endif 43 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Empty.c: -------------------------------------------------------------------------------- 1 | // This file intentionally left empty. 2 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/FlatShader.metal: -------------------------------------------------------------------------------- 1 | #import "include/RenderKitShaders.h" 2 | #import "include/FlatShader.h" 3 | 4 | typedef SimpleVertex Vertex; 5 | 6 | struct Fragment { 7 | float4 position [[position]]; // in projection space 8 | float3 modelPosition; 9 | float3 interpolatedNormalFlat[[flat]]; 10 | float3 interpolatedNormal; 11 | float2 textureCoordinate; 12 | ushort instance_id[[flat]]; 13 | }; 14 | 15 | // MARK: - 16 | 17 | [[vertex]] 18 | Fragment flatShaderVertexShader( 19 | Vertex in [[stage_in]], 20 | ushort instance_id[[instance_id]], 21 | constant CameraUniforms &camera [[buffer(1)]], 22 | constant ModelTransforms *modelTransforms [[buffer(2)]] 23 | ) 24 | { 25 | const ModelTransforms modelTransform = modelTransforms[instance_id]; 26 | const float4 modelVertex = modelTransform.modelViewMatrix * float4(in.position, 1.0); 27 | return { 28 | .position = camera.projectionMatrix * modelVertex, 29 | .modelPosition = float3(modelVertex) / modelVertex.w, 30 | .interpolatedNormalFlat = modelTransform.modelNormalMatrix * in.normal, 31 | .interpolatedNormal = modelTransform.modelNormalMatrix * in.normal, 32 | .textureCoordinate = in.textureCoordinate, 33 | .instance_id = instance_id 34 | }; 35 | } 36 | 37 | [[fragment]] 38 | vector_float4 flatShaderFragmentShader( 39 | Fragment in [[stage_in]], 40 | constant LightUniforms &lighting [[buffer(1)]], 41 | constant FlatMaterial *materials [[buffer(2)]], 42 | array, 128> textures [[texture(0)]] 43 | ) 44 | { 45 | auto material = materials[in.instance_id]; 46 | const auto diffuseMaterialColor = material.diffuseColor.xyz; 47 | const auto ambientMaterialColor= material.ambientColor.xyz; 48 | 49 | // Compute diffuse color 50 | const auto normal = normalize(in.interpolatedNormalFlat); 51 | const auto lightDirection = lighting.lightPosition - in.modelPosition; 52 | const auto lightDistanceSquared = length_squared(lightDirection); 53 | const auto lambertian = max(dot(lightDirection, normal), 0.0); 54 | const auto diffuseColor = diffuseMaterialColor * lambertian * lighting.lightColor * lighting.lightPower / lightDistanceSquared; 55 | 56 | // Compute ambient color 57 | const auto ambientColor = lighting.ambientLightColor * ambientMaterialColor; 58 | 59 | return float4(diffuseColor + ambientColor, 1.0); 60 | } 61 | 62 | // https://en.wikipedia.org/wiki/Blinn–Phong_reflection_model 63 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/GridShader.metal: -------------------------------------------------------------------------------- 1 | #import "include/RenderKitShaders.h" 2 | #import "include/GraphToy.h" 3 | 4 | using namespace metal; 5 | using namespace graphtoy; 6 | 7 | struct Fragment { 8 | float4 position [[position]]; // in projection space 9 | float3 modelPosition; 10 | float2 textureCoordinate; 11 | }; 12 | 13 | // MARK: - 14 | 15 | [[vertex]] 16 | Fragment gridVertexShader( 17 | SimpleVertex in [[stage_in]], 18 | ushort instance_id[[instance_id]], 19 | constant CameraUniforms &cameraUniforms [[buffer(1)]], 20 | constant ModelTransforms *instancedModelUniforms [[buffer(2)]] 21 | ) 22 | { 23 | const ModelTransforms modelUniforms = instancedModelUniforms[instance_id]; 24 | const float4 modelVertex = modelUniforms.modelViewMatrix * float4(in.position, 1.0); 25 | return { 26 | .position = cameraUniforms.projectionMatrix * modelVertex, 27 | .modelPosition = float3(modelVertex) / modelVertex.w, 28 | .textureCoordinate = in.textureCoordinate, 29 | }; 30 | } 31 | 32 | #define ddx dfdx 33 | #define ddy dfdy 34 | #define frac fract 35 | 36 | // https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8 37 | [[fragment]] 38 | float4 gridFragmentShader( 39 | Fragment in [[stage_in]]) 40 | { 41 | auto uv = in.textureCoordinate; 42 | const float2 lineWidth = {1.0, 1.0 }; 43 | float4 uvDDXY = float4(ddx(uv), ddy(uv)); // 44 | float2 uvDeriv = float2(length(uvDDXY.xz), length(uvDDXY.yw)); // 45 | bool2 invertLine = lineWidth > 0.5; 46 | float2 targetWidth = (invertLine.x || invertLine.y) ? 1.0 - lineWidth : lineWidth; 47 | float2 drawWidth = clamp(targetWidth, uvDeriv, 0.5); 48 | float2 lineAA = uvDeriv * 1.5; 49 | float2 gridUV = abs(frac(uv) * 2.0 - 1.0); 50 | gridUV = (invertLine.x || invertLine.y) ? gridUV : 1.0 - gridUV; 51 | float2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV); 52 | grid2 *= saturate(targetWidth / drawWidth); 53 | grid2 = lerp(grid2, targetWidth, saturate(uvDeriv * 2.0 - 1.0)); 54 | grid2 = (invertLine.x || invertLine.y) ? 1.0 - grid2 : grid2; 55 | float grid = lerp(grid2.x, 1.0, grid2.y); 56 | 57 | 58 | return { grid, grid, grid, 1.0 }; 59 | } 60 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Noise.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "include/GLSLCompat.h" 3 | //#include "include/Shaders.h" 4 | 5 | using namespace metal; 6 | using namespace glslCompatible; 7 | 8 | inline vec3 mod289(vec3 x) { 9 | return x - floor(x * (1.0 / 289.0)) * 289.0; 10 | } 11 | 12 | inline vec2 mod289(vec2 x) { 13 | return x - floor(x * (1.0 / 289.0)) * 289.0; 14 | } 15 | 16 | inline vec3 permute(vec3 x) { 17 | return mod289(((x*34.0)+10.0)*x); 18 | } 19 | 20 | float snoise(vec2 v) 21 | { 22 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 23 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 24 | -0.577350269189626, // -1.0 + 2.0 * C.x 25 | 0.024390243902439); // 1.0 / 41.0 26 | // First corner 27 | vec2 i = floor(v + dot(v, C.yy) ); 28 | vec2 x0 = v - i + dot(i, C.xx); 29 | 30 | // Other corners 31 | vec2 i1; 32 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 33 | //i1.y = 1.0 - i1.x; 34 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 35 | // x0 = x0 - 0.0 + 0.0 * C.xx ; 36 | // x1 = x0 - i1 + 1.0 * C.xx ; 37 | // x2 = x0 - 1.0 + 2.0 * C.xx ; 38 | vec4 x12 = x0.xyxy + C.xxzz; 39 | x12.xy -= i1; 40 | 41 | // Permutations 42 | i = mod289(i); // Avoid truncation effects in permutation 43 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) 44 | + i.x + vec3(0.0, i1.x, 1.0 )); 45 | 46 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); 47 | m = m*m ; 48 | m = m*m ; 49 | 50 | // Gradients: 41 points uniformly over a line, mapped onto a diamond. 51 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 52 | 53 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 54 | vec3 h = abs(x) - 0.5; 55 | vec3 ox = floor(x + 0.5); 56 | vec3 a0 = x - ox; 57 | 58 | // Normalise gradients implicitly by scaling m 59 | // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 60 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); 61 | 62 | // Compute final noise value at P 63 | vec3 g; 64 | g.x = a0.x * x0.x + h.x * x0.y; 65 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; 66 | return 130.0 * dot(m, g); 67 | } 68 | 69 | // MARK: - 70 | 71 | kernel void simplexNoise2D(uint2 gid [[thread_position_in_grid]], texture2d outputTexture [[texture(1)]]) { 72 | auto noise = snoise(float2(gid.x, gid.y)); 73 | outputTexture.write(float4(noise, 0, 0, 1), gid); 74 | } 75 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/PanoramicShaders.metal: -------------------------------------------------------------------------------- 1 | #import "include/RenderKitShaders.h" 2 | #import "include/PanoramicShaders.h" 3 | 4 | typedef SimpleVertex Vertex; 5 | 6 | struct Fragment { 7 | float4 position [[position]]; // in projection space 8 | float2 textureCoordinate; 9 | }; 10 | 11 | // MARK: - 12 | 13 | [[vertex]] 14 | Fragment panoramicVertexShader( 15 | Vertex in [[stage_in]], 16 | constant CameraUniforms &cameraUniforms [[buffer(1)]], 17 | constant float4x4 &modelViewMatrix [[buffer(2)]] 18 | ) 19 | { 20 | const float4 modelVertex = modelViewMatrix * float4(in.position, 1.0); 21 | // const float4 modelVertex = float4(in.position, 1.0); 22 | return { 23 | .position = cameraUniforms.projectionMatrix * modelVertex, 24 | .textureCoordinate = in.textureCoordinate 25 | }; 26 | } 27 | 28 | [[fragment]] 29 | vector_float4 panoramicFragmentShader( 30 | Fragment in [[stage_in]], 31 | constant PanoramaFragmentUniforms &uniforms[[buffer(0)]], 32 | array, 12> tiles [[texture(0)]] 33 | ) 34 | { 35 | float4 color; 36 | // Special-case single texture panoramas. 37 | if (tiles.size() == 1) { 38 | const auto texture = tiles[0]; 39 | color = texture.sample(RenderKitShaders::basicSampler, in.textureCoordinate); 40 | } 41 | else { 42 | const float2 denormalizedTextureCoordinate = in.textureCoordinate * float2(uniforms.gridSize); 43 | const ushort textureIndex = clamp(ushort(denormalizedTextureCoordinate.y) * uniforms.gridSize.x + ushort(denormalizedTextureCoordinate.x), 0, ushort(tiles.size()) - 1); 44 | const float2 textureCoordinate = denormalizedTextureCoordinate - floor(denormalizedTextureCoordinate); 45 | const auto texture = tiles[textureIndex]; 46 | // const float d = 1.0 / float(texture.get_width()); 47 | // if (textureCoordinate.x <= d || textureCoordinate.x >= 1 - d || textureCoordinate.y < d || textureCoordinate.y > 1 - d) { 48 | // return float4(1, 0, 0, 1); 49 | // } 50 | color = texture.sample(RenderKitShaders::basicSampler, textureCoordinate); 51 | } 52 | color *= uniforms.colorFactor; 53 | return color; 54 | } 55 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/ParticlesShader.metal: -------------------------------------------------------------------------------- 1 | #import "include/RenderKitShaders.h" 2 | 3 | typedef SimpleVertex Vertex; 4 | 5 | struct Fragment { 6 | float4 position [[position]]; // in projection space 7 | float2 textureCoordinate; 8 | }; 9 | 10 | // MARK: - 11 | 12 | [[vertex]] 13 | Fragment particleVertexShader( 14 | Vertex in [[stage_in]], 15 | ushort instance_id[[instance_id]], 16 | constant CameraUniforms &cameraUniforms [[buffer(1)]], 17 | constant float2 *positions [[buffer(2)]] 18 | ) 19 | { 20 | const float particleSize = 10.0; 21 | 22 | auto position = positions[instance_id]; 23 | const float4 modelVertex = float4(in.position * particleSize * 2 + float3(position, 0), 1); 24 | return { 25 | .position = cameraUniforms.projectionMatrix * modelVertex, 26 | .textureCoordinate = in.textureCoordinate 27 | }; 28 | } 29 | 30 | [[fragment]] 31 | vector_float4 particleFragmentShader( 32 | Fragment in [[stage_in]] 33 | ) 34 | { 35 | const float d = 0.25 - distance_squared(in.textureCoordinate, float2(0.5, 0.5)); 36 | return float4(1, 0, 0, d > 0 ? 1.0 : 0.0); 37 | } 38 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/ReallyFlatShader.metal: -------------------------------------------------------------------------------- 1 | #import "include/RenderKitShaders.h" 2 | 3 | typedef SimpleVertex Vertex; 4 | 5 | struct Fragment { 6 | float4 position [[position]]; // in projection space 7 | }; 8 | 9 | // MARK: - 10 | 11 | [[vertex]] 12 | Fragment reallyFlatVertexShader( 13 | Vertex in [[stage_in]], 14 | ushort instance_id[[instance_id]], 15 | constant CameraUniforms &cameraUniforms [[buffer(1)]], 16 | constant float2 &position [[buffer(2)]] 17 | ) 18 | { 19 | const float4 modelVertex = float4(in.position + float3(position, 0), 1); 20 | return { 21 | .position = cameraUniforms.projectionMatrix * modelVertex, 22 | }; 23 | } 24 | 25 | [[fragment]] 26 | vector_float4 reallyFlatFragmentShader( 27 | Fragment in [[stage_in]] 28 | ) 29 | { 30 | return float4(1, 0, 0, 1); 31 | } 32 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Scratch/DemoBlit.metal: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | using namespace metal; 5 | 6 | struct Vertex { 7 | vector_float3 position [[attribute(0)]]; 8 | vector_float3 normal [[attribute(1)]]; 9 | vector_float2 textureCoords [[attribute(2)]]; 10 | }; 11 | 12 | struct DemoBlitVertexUniforms { 13 | simd_float3x4 transform; 14 | }; 15 | 16 | struct Fragment { 17 | vector_float4 position [[position]]; 18 | float2 textureCoords; 19 | }; 20 | 21 | // MARK: - 22 | 23 | enum DemoBlitID { 24 | kDemoBlitID_Vertices = 0, 25 | kDemoBlitID_VertexShaderUniforms = 1, 26 | kDemoBlitID_BaseColorTexture = 0, 27 | kDemoBlitID_BaseColorSampler = 0, 28 | }; 29 | 30 | // MARK: - 31 | 32 | [[vertex]] 33 | Fragment demoBlitVertexShader( 34 | Vertex vertexIn [[stage_in]], 35 | constant DemoBlitVertexUniforms &uniforms [[buffer(kDemoBlitID_VertexShaderUniforms)]] 36 | ) 37 | { 38 | return { 39 | .position = vector_float4(vector_float4(vertexIn.position, 1.0) * uniforms.transform, 1.0), 40 | .textureCoords = vertexIn.textureCoords 41 | }; 42 | } 43 | 44 | [[fragment]] 45 | vector_float4 demoBlitFragmentShader( 46 | Fragment fragmentIn [[stage_in]], 47 | texture2d texture [[texture(kDemoBlitID_BaseColorTexture)]], 48 | sampler sampler[[sampler(kDemoBlitID_BaseColorSampler)]] 49 | ) 50 | { 51 | return texture.sample(sampler, fragmentIn.textureCoords); 52 | } 53 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/Scratch/MeshShaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | //#include 3 | //#include 4 | // 5 | //using namespace metal; 6 | // 7 | //struct CurvePayload { 8 | // 9 | //}; 10 | // 11 | //[[object]] 12 | //void objectShader( 13 | // object_data CurvePayload *payloadOutput [[payload]], 14 | // const device void *inputData [[buffer(0)]], 15 | // uint thread_index_in_threadgroup [[thread_index_in_threadgroup]], 16 | // uint triangleID [[threadgroup_position_in_grid]], 17 | // metal::mesh_grid_properties mgp 18 | // ushort instance_id [[instance_id]] 19 | // 20 | // ) 21 | //{ 22 | //// if (hairID < kHairsPerBlock) 23 | //// payloadOutput[hairID] = generateCurveData(inputData, hairID, triangleID); 24 | //// if (thread_index_in_threadgroup == 0) 25 | //// mgp.set_threadgroups_per_grid(uint3(kHairPerBlockX, kHairPerBlockY, 1)); 26 | //} 27 | // 28 | //struct VertexData { float4 position [[position]]; }; 29 | //struct PrimitiveData { float4 color; }; 30 | // 31 | //using triangle_mesh_t = metal::mesh< 32 | // VertexData, // Vertex type 33 | // PrimitiveData, // Primitive type 34 | // 10, // Maximum vertices 35 | // 6, // Maximum primitives 36 | // metal::topology::triangle // Topology 37 | //>; 38 | // 39 | //[[mesh]] void myMeshShader(triangle_mesh_t outputMesh, 40 | // uint tid [[thread_index_in_threadgroup]]) 41 | //{ 42 | // 43 | //// if (tid < kVertexCount) 44 | //// outputMesh.set_vertex(tid, calculateVertex(tid)); 45 | //// 46 | //// if (tid < kIndexCount) 47 | //// outputMesh.set_index(tid, calculateIndex(tid)); 48 | //// 49 | //// if (tid < kPrimitiveCount) 50 | //// outputMesh.set_primitive(tid, calculatePrimitive(tid)); 51 | //// 52 | ////if (tid == 0) 53 | //// outputMesh.set_primitive_count(kPrimitiveCount); 54 | // 55 | //} 56 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/UnlitShader.metal: -------------------------------------------------------------------------------- 1 | #import "include/RenderKitShaders.h" 2 | #import "include/UnlitShader.h" 3 | 4 | typedef SimpleVertex Vertex; 5 | 6 | struct Fragment { 7 | float4 position [[position]]; // in projection space 8 | float2 textureCoordinate; 9 | ushort instance_id [[flat]]; // TODO: is flat needed? 10 | }; 11 | 12 | // MARK: - 13 | 14 | [[vertex]] 15 | Fragment unlitVertexShader( 16 | Vertex in [[stage_in]], 17 | ushort instance_id[[instance_id]], 18 | constant CameraUniforms &camera[[buffer(1)]], 19 | constant ModelTransforms *models[[buffer(2)]] // TODO: rename to modelTransforms 20 | ) 21 | { 22 | const ModelTransforms model = models[instance_id]; 23 | const float4 modelVertex = model.modelViewMatrix * float4(in.position, 1.0); 24 | return { 25 | .position = camera.projectionMatrix * modelVertex, 26 | .textureCoordinate = in.textureCoordinate, 27 | .instance_id = instance_id, 28 | }; 29 | } 30 | 31 | [[fragment]] 32 | vector_float4 unlitFragmentShader( 33 | Fragment in [[stage_in]], 34 | constant UnlitMaterial *materials [[buffer(0)]], 35 | array, 1> textures [[texture(0)]] 36 | ) 37 | { 38 | float4 color; 39 | auto material = materials[in.instance_id]; 40 | if (material.textureIndex == -1) { 41 | color = materials[in.instance_id].color; 42 | } 43 | else { 44 | auto texture = textures[material.textureIndex]; 45 | color = texture.sample(RenderKitShaders::basicSampler, in.textureCoordinate); 46 | } 47 | return color; 48 | } 49 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/VolumeShaders.metal: -------------------------------------------------------------------------------- 1 | #import "include/RenderKitShaders.h" 2 | #import "include/VolumeShaders.h" 3 | 4 | typedef SimpleVertex Vertex; 5 | 6 | struct VertexOut { 7 | float4 position [[position]]; // in projection space 8 | float3 textureCoordinate; 9 | }; 10 | typedef VertexOut FragmentIn; 11 | 12 | // MARK: - 13 | 14 | [[vertex]] 15 | VertexOut volumeVertexShader( 16 | Vertex in [[stage_in]], 17 | constant CameraUniforms &cameraUniforms [[buffer(1)]], 18 | constant VolumeTransforms &transforms [[buffer(2)]], 19 | constant VolumeInstance *instances [[buffer(3)]], 20 | ushort instance_id [[instance_id]] 21 | ) 22 | { 23 | const VolumeInstance instance = instances[instance_id]; 24 | const float4 offset = float4(0, 0, instance.offsetZ, 1.0); 25 | const float3 textureCoordinate = float3(in.textureCoordinate, instance.textureZ); 26 | 27 | // Kill rotation from modelView matrix. 28 | // TODO: This also kills scale. 29 | float4x4 modelViewMatrix = transforms.modelViewMatrix; 30 | // TODO: Hack the scale in here until i can bothered to fix this. 31 | modelViewMatrix[0][0] = 2.0; 32 | modelViewMatrix[0][1] = 0.0; 33 | modelViewMatrix[0][2] = 0.0; 34 | modelViewMatrix[1][0] = 0.0; 35 | modelViewMatrix[1][1] = 2.0; 36 | modelViewMatrix[1][2] = 0.0; 37 | modelViewMatrix[2][0] = 0.0; 38 | modelViewMatrix[2][1] = 0.0; 39 | modelViewMatrix[2][2] = 2.0; 40 | 41 | float4x4 textureMatrix = transforms.textureMatrix; 42 | float3 rotatedTextureCoordinate = (textureMatrix * float4(textureCoordinate.xyz, 1.0)).xzy; 43 | const float4 modelVertex = modelViewMatrix * float4(in.position + offset.xyz, 1.0); 44 | const float4 clipSpace = cameraUniforms.projectionMatrix * modelVertex; 45 | return { 46 | .position = clipSpace, 47 | .textureCoordinate = rotatedTextureCoordinate, 48 | }; 49 | } 50 | 51 | // MARK: - 52 | 53 | [[fragment]] 54 | float4 volumeFragmentShader( 55 | FragmentIn in [[stage_in]], 56 | texture3d texture [[texture(0)]], 57 | texture1d transferFunctionTexture [[texture(1)]], 58 | constant VolumeFragmentUniforms &uniforms [[buffer(0)]] 59 | ) 60 | { 61 | if ( 62 | in.textureCoordinate.x < 0 || in.textureCoordinate.x > 1.0 63 | || in.textureCoordinate.y < 0 || in.textureCoordinate.y > 1.0 64 | || in.textureCoordinate.z < 0 || in.textureCoordinate.z > 1.0 65 | ) { 66 | discard_fragment(); 67 | } 68 | constexpr struct sampler basicSampler(coord::normalized, address::clamp_to_edge, filter::linear); 69 | const float normalizedValue = texture.sample(basicSampler, in.textureCoordinate).r / float(uniforms.maxValue); 70 | auto color = transferFunctionTexture.sample(basicSampler, normalizedValue); 71 | 72 | // Alpha adjusted by number of instances so we don't blow out the brightness. 73 | return color * float4(1, 1, 1, 1 / float(uniforms.instanceCount) * uniforms.alpha); 74 | } 75 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/immersiveShaders.metal: -------------------------------------------------------------------------------- 1 | #import "include/RenderKitShaders.h" 2 | #import "include/ImmersiveShaders.h" 3 | 4 | using namespace metal; 5 | 6 | typedef struct 7 | { 8 | float3 position [[attribute(VertexAttributePosition)]]; 9 | float2 texCoord [[attribute(VertexAttributeTexcoord)]]; 10 | } Vertex; 11 | 12 | typedef struct 13 | { 14 | float4 position [[position]]; 15 | float2 texCoord; 16 | } VertexOut; 17 | 18 | typedef VertexOut ShaderIn; 19 | 20 | vertex VertexOut vertexShader( 21 | Vertex in [[stage_in]], 22 | ushort amp_id [[amplification_id]], 23 | constant UniformsArray &uniformsArray [[buffer(BufferIndexUniforms)]]) 24 | { 25 | const Uniforms uniforms = uniformsArray.uniforms[amp_id]; 26 | const float4 position = float4(in.position, 1.0); 27 | return { 28 | .position = uniforms.projectionMatrix * uniforms.modelViewMatrix * position, 29 | .texCoord = in.texCoord, 30 | }; 31 | } 32 | 33 | fragment float4 fragmentShader( 34 | ShaderIn in [[stage_in]], 35 | texture2d colorMap [[texture(TextureIndexColor)]]) 36 | { 37 | constexpr sampler colorSampler(mip_filter::linear, mag_filter::linear, min_filter::linear); 38 | half4 colorSample = colorMap.sample(colorSampler, in.texCoord.xy); 39 | return float4(colorSample); 40 | } 41 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/CommonTypes.h: -------------------------------------------------------------------------------- 1 | #import "RenderKitShaders.h" 2 | #import "MetalSupport.h" 3 | 4 | #ifdef __METAL_VERSION__ 5 | struct SimpleVertex { 6 | float3 position [[attribute(0)]]; 7 | float3 normal [[attribute(1)]]; 8 | float2 textureCoordinate [[attribute(2)]]; 9 | }; 10 | #else 11 | typedef struct { 12 | float x; 13 | float y; 14 | float z; 15 | } PackedFloat3; 16 | 17 | struct SimpleVertex { 18 | PackedFloat3 packedPosition; 19 | PackedFloat3 packedNormal; 20 | float2 textureCoordinate; 21 | }; 22 | #endif 23 | 24 | struct ModelTransforms { 25 | float4x4 modelViewMatrix; // model space -> camera space 26 | float3x3 modelNormalMatrix; // model space - used for non-uniform scaled normal transformation. See https://www.youtube.com/watch?v=esC1HnyD9Bk&list=PLplnkTzzqsZS3R5DjmCQsqupu43oS9CFN 27 | }; 28 | 29 | // TODO: DEPRECATE 30 | struct ModelUniforms { 31 | float4x4 modelViewMatrix; // model space -> camera space 32 | float3x3 modelNormalMatrix; // model space - used for non-uniform scaled normal transformation. See https://www.youtube.com/watch?v=esC1HnyD9Bk&list=PLplnkTzzqsZS3R5DjmCQsqupu43oS9CFN 33 | float4 color; 34 | }; 35 | 36 | struct CameraUniforms { 37 | float4x4 projectionMatrix; 38 | }; 39 | 40 | struct LightUniforms { 41 | // Per diffuse light 42 | float3 lightPosition; 43 | float3 lightColor; 44 | float lightPower; 45 | // Per environment 46 | float3 ambientLightColor; 47 | }; 48 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/FlatShader.h: -------------------------------------------------------------------------------- 1 | #import "RenderKitShaders.h" 2 | 3 | // TODO: Rename 4 | struct FlatMaterial { 5 | float4 diffuseColor; 6 | short diffuseTextureIndex; // or -1 for no texture 7 | float4 ambientColor; 8 | short ambientTextureIndex; // or -1 for no texture 9 | }; 10 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/GLSLCompat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __METAL_VERSION__ 4 | #include 5 | #endif 6 | 7 | using namespace metal; 8 | 9 | namespace glslCompatible { 10 | // GLSL Compat 11 | 12 | typedef float2 vec2; 13 | typedef float3 vec3; 14 | typedef float4 vec4; 15 | 16 | inline float2 mod(float2 lhs, float rhs) { 17 | return fmod(lhs, rhs); 18 | } 19 | 20 | inline float3 mod(float3 lhs, float rhs) { 21 | return fmod(lhs, rhs); 22 | } 23 | 24 | inline float3 mod(float3 lhs, float3 rhs) { 25 | return fmod(lhs, rhs); 26 | } 27 | 28 | inline float3 frac(float3 v) { 29 | return fract(v); 30 | } 31 | 32 | inline float2 frac(float2 v) { 33 | return fract(v); 34 | } 35 | 36 | inline float frac(float v) { 37 | return fract(v); 38 | } 39 | 40 | 41 | // MARK: - 42 | 43 | //inline float random (vec2 st) { 44 | // return fract(sin(dot(st.xy, 45 | // vec2(12.9898,78.233)))* 46 | // 43758.5453123); 47 | //} 48 | 49 | //// MARK: - 50 | // 51 | //inline float rand2dTo1d(vec2 input) { 52 | // return random(input); 53 | //} 54 | // 55 | ////inline float rand3dTo1d(vec3 input) { 56 | //// return random(input); 57 | ////} 58 | // 59 | //inline vec2 rand2dTo2d(vec2 input) { 60 | // auto x = random(vec2(input.x * 2, input.y)); 61 | // auto y = random(vec2(input.x * 2 + 1, input.y)); 62 | // return vec2(x, y); 63 | //} 64 | // 65 | //inline float3 rand1dTo3d(float input) { 66 | // return float3( 67 | // random(input * 3), 68 | // random(input * 3 + 1), 69 | // random(input * 3 + 2) 70 | // ); 71 | //} 72 | } 73 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/ImmersiveShaders.h: -------------------------------------------------------------------------------- 1 | #import "MetalSupport.h" 2 | 3 | typedef NS_ENUM(NSInteger, BufferIndex) 4 | { 5 | BufferIndexMeshPositions = 0, 6 | BufferIndexMeshGenerics = 1, 7 | BufferIndexUniforms = 2 8 | }; 9 | 10 | typedef NS_ENUM(NSInteger, VertexAttribute) 11 | { 12 | VertexAttributePosition = 0, 13 | VertexAttributeTexcoord = 1, 14 | }; 15 | 16 | typedef NS_ENUM(NSInteger, TextureIndex) 17 | { 18 | TextureIndexColor = 0, 19 | }; 20 | 21 | typedef struct 22 | { 23 | matrix_float4x4 projectionMatrix; 24 | matrix_float4x4 modelViewMatrix; 25 | } Uniforms; 26 | 27 | typedef struct 28 | { 29 | Uniforms uniforms[2]; 30 | } UniformsArray; 31 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/MetalSupport.h: -------------------------------------------------------------------------------- 1 | #ifndef __METAL_VERSION__ 2 | 3 | #import 4 | #import 5 | 6 | typedef simd_float2 float2; 7 | typedef simd_float3 float3; 8 | typedef simd_float4 float4; 9 | typedef simd_float3x3 float3x3; 10 | typedef simd_float4x4 float4x4; 11 | 12 | #else 13 | 14 | #import 15 | #import 16 | 17 | #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type 18 | #define NSInteger metal::int32_t 19 | 20 | using namespace metal; 21 | 22 | namespace RenderKitShaders { 23 | constexpr sampler basicSampler(coord::normalized, address::clamp_to_edge, filter::linear); 24 | }; 25 | 26 | template T srgb_to_linear(T c) { 27 | if (c <= 0.04045) { 28 | return c / 12.92; 29 | } 30 | else { 31 | return powr((c + 0.055) / 1.055, 2.4); 32 | } 33 | } 34 | 35 | inline float3 srgb_to_linear(float3 c) { 36 | return float3(srgb_to_linear(c.x), srgb_to_linear(c.y), srgb_to_linear(c.z)); 37 | } 38 | 39 | inline float4 srgb_to_linear(float4 c) { 40 | return float4(srgb_to_linear(c.xyz), c.a); 41 | } 42 | 43 | template T linear_to_srgb(T c) { 44 | if (isnan(c)) { 45 | return 0.0; 46 | } 47 | else if (c > 1.0) { 48 | return 1.0; 49 | } 50 | else { 51 | return (c < 0.0031308f) ? (12.92f * c) : (1.055 * powr(c, 1.0 / 2.4) - 0.055); 52 | } 53 | } 54 | 55 | inline float3 linear_to_srgb(float3 c) { 56 | return float3(linear_to_srgb(c.x), linear_to_srgb(c.y), linear_to_srgb(c.z)); 57 | } 58 | 59 | inline float4 linear_to_srgb(float4 c) { 60 | return float4(linear_to_srgb(c.xyz), c.a); 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/PanoramicShaders.h: -------------------------------------------------------------------------------- 1 | #import "RenderKitShaders.h" 2 | 3 | struct PanoramaFragmentUniforms { 4 | simd_ushort2 gridSize; 5 | float4 colorFactor; 6 | }; 7 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/Random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GLSLCompat.h" 4 | 5 | using namespace glslCompatible; 6 | 7 | // https://www.ronja-tutorials.com/post/024-white-noise/ 8 | 9 | //get a scalar random value from a 3d value 10 | inline float rand3dTo1d(float3 value, float3 dotDir = float3(12.9898, 78.233, 37.719)){ 11 | //make value smaller to avoid artefacts 12 | float3 smallValue = sin(value); 13 | //get scalar value from 3d vector 14 | float random = dot(smallValue, dotDir); 15 | //make value more random by making it bigger and then taking the factional part 16 | random = frac(sin(random) * 143758.5453); 17 | return random; 18 | } 19 | 20 | inline float rand2dTo1d(float2 value, float2 dotDir = float2(12.9898, 78.233)){ 21 | float2 smallValue = sin(value); 22 | float random = dot(smallValue, dotDir); 23 | random = frac(sin(random) * 143758.5453); 24 | return random; 25 | } 26 | 27 | inline float rand1dTo1d(float3 value, float mutator = 0.546){ 28 | float3 random = frac(sin(value + mutator) * 143758.5453); 29 | return random.x; 30 | } 31 | 32 | //to 2d functions 33 | 34 | inline float2 rand3dTo2d(float3 value){ 35 | return float2( 36 | rand3dTo1d(value, float3(12.989, 78.233, 37.719)), 37 | rand3dTo1d(value, float3(39.346, 11.135, 83.155)) 38 | ); 39 | } 40 | 41 | inline float2 rand2dTo2d(float2 value){ 42 | return float2( 43 | rand2dTo1d(value, float2(12.989, 78.233)), 44 | rand2dTo1d(value, float2(39.346, 11.135)) 45 | ); 46 | } 47 | 48 | inline float2 rand1dTo2d(float value){ 49 | return float2( 50 | rand2dTo1d(value, 3.9812), 51 | rand2dTo1d(value, 7.1536) 52 | ); 53 | } 54 | 55 | //to 3d functions 56 | 57 | inline float3 rand3dTo3d(float3 value){ 58 | return float3( 59 | rand3dTo1d(value, float3(12.989, 78.233, 37.719)), 60 | rand3dTo1d(value, float3(39.346, 11.135, 83.155)), 61 | rand3dTo1d(value, float3(73.156, 52.235, 09.151)) 62 | ); 63 | } 64 | 65 | inline float3 rand2dTo3d(float2 value){ 66 | return float3( 67 | rand2dTo1d(value, float2(12.989, 78.233)), 68 | rand2dTo1d(value, float2(39.346, 11.135)), 69 | rand2dTo1d(value, float2(73.156, 52.235)) 70 | ); 71 | } 72 | 73 | inline float3 rand1dTo3d(float value){ 74 | return float3( 75 | rand1dTo1d(value, 3.9812), 76 | rand1dTo1d(value, 7.1536), 77 | rand1dTo1d(value, 5.7241) 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/RenderKitShaders.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #import "CommonTypes.h" 4 | #import "MetalSupport.h" 5 | #import "ImmersiveShaders.h" 6 | #import "PanoramicShaders.h" 7 | #import "VolumeShaders.h" 8 | #import "FlatShader.h" 9 | #import "UnlitShader.h" 10 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/UnlitShader.h: -------------------------------------------------------------------------------- 1 | #import "RenderKitShaders.h" 2 | 3 | struct UnlitMaterial { 4 | float4 color; 5 | short textureIndex; // or -1 for no texture 6 | }; 7 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/include/VolumeShaders.h: -------------------------------------------------------------------------------- 1 | #import "RenderKitShaders.h" 2 | 3 | #pragma pack(push, 1) 4 | struct VolumeTransforms { 5 | simd_float4x4 modelViewMatrix; 6 | simd_float4x4 textureMatrix; 7 | }; 8 | #pragma pack(pop) 9 | 10 | #pragma pack(push, 1) 11 | struct VolumeFragmentUniforms { 12 | unsigned short instanceCount; 13 | unsigned short maxValue; 14 | float alpha; 15 | }; 16 | #pragma pack(pop) 17 | 18 | 19 | #pragma pack(push, 1) 20 | struct VolumeInstance { 21 | float offsetZ; 22 | float textureZ; 23 | }; 24 | #pragma pack(pop) 25 | -------------------------------------------------------------------------------- /Sources/RenderKitShaders/voronoiNoise.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "include/GLSLCompat.h" 3 | #include "include/Random.h" 4 | //#include "include/Shaders.h" 5 | 6 | using namespace metal; 7 | 8 | float2 voronoiNoise(float3 value) { 9 | float2 baseCell = floor(value.xy); 10 | 11 | float minDistToCell = 10; 12 | float2 closestCell; 13 | for(int x=-1; x<=1; x++){ 14 | for(int y=-1; y<=1; y++){ 15 | float2 cell = baseCell + float2(x, y); 16 | float2 cellPosition = cell + rand2dTo2d(cell); 17 | float2 toCell = cellPosition - value.xy; 18 | float distToCell = length(toCell); 19 | if(distToCell < minDistToCell){ 20 | minDistToCell = distToCell; 21 | closestCell = cell; 22 | } 23 | } 24 | } 25 | float random = rand2dTo1d(closestCell); 26 | return float2(minDistToCell, random); 27 | } 28 | 29 | // https://www.ronja-tutorials.com/post/029-tiling-noise/ 30 | float3 implVoronoiNoise(float3 value){ 31 | float2 baseCell = floor(value.xy); 32 | 33 | //first pass to find the closest cell 34 | float minDistToCell = 10; 35 | float2 toClosestCell; 36 | float2 closestCell; 37 | for(int x1=-1; x1<=1; x1++){ 38 | for(int y1=-1; y1<=1; y1++){ 39 | float2 cell = baseCell + float2(x1, y1); 40 | float2 cellPosition = cell + rand2dTo2d(cell); 41 | float2 toCell = cellPosition - value.xy; 42 | float distToCell = length(toCell); 43 | if(distToCell < minDistToCell){ 44 | minDistToCell = distToCell; 45 | closestCell = cell; 46 | toClosestCell = toCell; 47 | } 48 | } 49 | } 50 | 51 | //second pass to find the distance to the closest edge 52 | float minEdgeDistance = 10; 53 | for(int x2=-1; x2<=1; x2++){ 54 | for(int y2=-1; y2<=1; y2++){ 55 | float2 cell = baseCell + float2(x2, y2); 56 | float2 cellPosition = cell + rand2dTo2d(cell); 57 | float2 toCell = cellPosition - value.xy; 58 | 59 | float2 diffToClosestCell = abs(closestCell - cell); 60 | bool isClosestCell = diffToClosestCell.x + diffToClosestCell.y < 0.1; 61 | if(!isClosestCell){ 62 | float2 toCenter = (toClosestCell + toCell) * 0.5; 63 | float2 cellDifference = normalize(toCell - toClosestCell); 64 | float edgeDistance = dot(toCenter, cellDifference); 65 | minEdgeDistance = min(minEdgeDistance, edgeDistance); 66 | } 67 | } 68 | } 69 | 70 | float random = rand2dTo1d(closestCell); 71 | 72 | // distance, "id", edge distance 73 | return float3(minDistToCell, random, minEdgeDistance); 74 | } 75 | 76 | // MARK: - 77 | 78 | struct VoronoiNoise { 79 | float2 size; 80 | float2 offset; 81 | short mode; 82 | texture2d outputTexture; 83 | }; 84 | 85 | [[kernel]] 86 | void voronoiNoiseCompute( 87 | constant VoronoiNoise &argument[[buffer(0)]], 88 | uint2 gid [[thread_position_in_grid]] 89 | ) { 90 | auto noise = implVoronoiNoise(float3((float2(gid.x, gid.y) + argument.offset) / argument.size, 0)); 91 | float4 color; 92 | if (argument.mode == 0) { 93 | float value = noise.x; 94 | color = float4(value, value, value, 1); 95 | } 96 | else if (argument.mode == 1) { 97 | color = float4(rand1dTo3d(noise.y), 1); 98 | } 99 | else if (argument.mode == 2) { 100 | float value = noise.z; 101 | color = float4(value, value, value, 1); 102 | } 103 | else if (argument.mode == 3) { 104 | float value = step(noise.z, 0.05); 105 | color = float4(value, value, value, 1); 106 | } 107 | else { 108 | color = float4(1, 0, 1, 1); 109 | } 110 | argument.outputTexture.write(color, gid); 111 | } 112 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ## Short Term 4 | 5 | - [ ] Get GLTF Barramundi Rendering 6 | - [ ] Bindings are incorrect 7 | - [ ] Use Metal introspection to map names <-> bindings 8 | - [ ] Get point cloud view 9 | 10 | ## Medium Term 11 | 12 | - [ ] Huge tidy up: get most code into RenderKit 13 | - [ ] Start on uber PBR renderer 14 | - [ ] Compute based fluids system 15 | -------------------------------------------------------------------------------- /Tests/RenderKitTests/CSGTests.swift: -------------------------------------------------------------------------------- 1 | @testable import RenderKitScratch 2 | import XCTest 3 | 4 | class CSGTests: XCTestCase { 5 | func test1() { 6 | let sphere = Sphere(center: .zero, radius: 10) 7 | let plane = Plane(normal: [0, 0, 1], w: 0) 8 | let csg = sphere.toCSG() 9 | print(csg.polygons.count) 10 | let (a, b) = csg.split(plane: plane) 11 | print(a.polygons.count) 12 | print(b.polygons.count) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/RenderKitTests/RenderKit3Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import RenderKit 3 | 4 | final class RenderKitTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/RenderKitTests/ScratchTests.swift: -------------------------------------------------------------------------------- 1 | @testable import RenderKitScratch 2 | import XCTest 3 | 4 | final class ScratchTests: XCTestCase { 5 | // func testLineSegmentLength() throws { 6 | // let line = LineSegment3D(start: .init(x: 0, y: 0, z: 0), end: .init(x: 3, y: 4, z: 0)) 7 | // XCTAssertEqual(line.length, 5) 8 | // } 9 | // 10 | // func testParallelLines() throws { 11 | // let line1 = Line3D(point: .init(x: 0, y: 0, z: 0), direction: .init(x: 1, y: 0, z: 0)) 12 | // let line2 = Line3D(point: .init(x: 0, y: 1, z: 0), direction: .init(x: 1, y: 0, z: 0)) 13 | // XCTAssertTrue(line1.parallel(to: line2)) 14 | // } 15 | // 16 | // func testNonParallelLines() throws { 17 | // let line1 = Line3D(point: .init(x: 0, y: 0, z: 0), direction: .init(x: 1, y: 0, z: 0)) 18 | // let line2 = Line3D(point: .init(x: 0, y: 1, z: 0), direction: .init(x: 0, y: 1, z: 0)) 19 | // XCTAssertFalse(line1.parallel(to: line2)) 20 | // } 21 | // 22 | // func testLineIntersects() throws { 23 | // let line1 = Line3D(point: .init(x: 0, y: 0, z: 0), direction: .init(x: 1, y: 0, z: 0)) 24 | // let line2 = Line3D(point: .init(x: 0, y: 1, z: 0), direction: .init(x: 0, y: 1, z: 0)) 25 | // XCTAssertTrue(line1.intersects(other: line2)) 26 | // } 27 | // 28 | // func testLineNonIntersects() throws { 29 | // let line1 = Line3D(point: .init(x: 0, y: 0, z: 0), direction: .init(x: 1, y: 0, z: 0)) 30 | // let line2 = Line3D(point: .init(x: 0, y: 1, z: 0), direction: .init(x: 1, y: 0, z: 0)) 31 | // XCTAssertFalse(line1.intersects(other: line2)) 32 | // } 33 | // 34 | // func testIntersectionPoint() throws { 35 | // let line1 = Line3D(point: .init(x: 0, y: 0, z: 0), direction: .init(x: 1, y: 0, z: 0)) 36 | // let line2 = Line3D(point: .init(x: 0, y: 1, z: 0), direction: .init(x: 0, y: 1, z: 0)) 37 | // let intersection = line1.intersectionPoint(other: line2) 38 | // XCTAssertEqual(intersection, .init(x: 0, y: 0, z: 0)) 39 | // } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/RenderKitTests/VertexDescriptorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import RenderKit 3 | 4 | final class VertexDescriptorTests: XCTestCase { 5 | func testVertexDescriptor() throws { 6 | let d = VertexDescriptor.packed(semantics: [.position, .normal, .textureCoordinate]) 7 | XCTAssertEqual(d.layouts[0].attributes[0], .init(semantic: .position, format: .float3, offset: 0)) 8 | XCTAssertEqual(d.layouts[0].attributes[1], .init(semantic: .normal, format: .float3, offset: 12)) 9 | XCTAssertEqual(d.layouts[0].attributes[2], .init(semantic: .textureCoordinate, format: .float2, offset: 24)) 10 | XCTAssertEqual(d.layouts[0].stepFunction, .perVertex) 11 | XCTAssertEqual(d.layouts[0].stepRate, 1) 12 | XCTAssertEqual(d.layouts[0].stride, 32) 13 | 14 | let mtl = MTLVertexDescriptor(d) 15 | XCTAssertEqual(mtl.attributes[0].format, .float3) 16 | XCTAssertEqual(mtl.attributes[0].offset, 0) 17 | XCTAssertEqual(mtl.attributes[0].bufferIndex, 0) 18 | XCTAssertEqual(mtl.attributes[1].format, .float3) 19 | XCTAssertEqual(mtl.attributes[1].offset, 12) 20 | XCTAssertEqual(mtl.attributes[1].bufferIndex, 0) 21 | XCTAssertEqual(mtl.attributes[2].format, .float2) 22 | XCTAssertEqual(mtl.attributes[2].offset, 24) 23 | XCTAssertEqual(mtl.attributes[2].bufferIndex, 0) 24 | XCTAssertEqual(mtl.layouts[0].stepFunction, .perVertex) 25 | XCTAssertEqual(mtl.layouts[0].stepRate, 1) 26 | XCTAssertEqual(mtl.layouts[0].stride, 32) 27 | 28 | var d2 = try VertexDescriptor(mtl) 29 | XCTAssertNil(d2.layouts[0].attributes[0].semantic) 30 | XCTAssertNil(d2.layouts[0].attributes[1].semantic) 31 | XCTAssertNil(d2.layouts[0].attributes[2].semantic) 32 | d2.layouts[0].attributes[0].semantic = .position 33 | d2.layouts[0].attributes[1].semantic = .normal 34 | d2.layouts[0].attributes[2].semantic = .textureCoordinate 35 | XCTAssertEqual(d, d2) 36 | 37 | /* 38 | 39 | Buffer 0: 40 | stepFunction = MTLVertexStepFunctionPerVertex 41 | stride = 32 42 | Attribute 0: 43 | offset = 0 44 | format = MTLAttributeFormatFloat3 45 | Attribute 1: 46 | offset = 12 47 | format = MTLAttributeFormatFloat3 48 | Attribute 2: 49 | offset = 24 50 | format = MTLAttributeFormatFloat2 51 | */ 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/RenderKitTests/WingedEdgeTests.swift: -------------------------------------------------------------------------------- 1 | @testable import RenderKitScratch 2 | import simd 3 | import XCTest 4 | 5 | final class CircularPairsTests: XCTestCase { 6 | func testCircularPairsEmpty() throws { 7 | let array: [Int] = [] 8 | let result = Array(array.circularPairs()) 9 | XCTAssertTrue(result.isEmpty) 10 | } 11 | 12 | func testCircularPairsMultiple() throws { 13 | let array = [1, 2, 3, 4] 14 | let result = Array(array.circularPairs()) 15 | XCTAssertEqual(result.count, 4) 16 | XCTAssertEqual(result.map(\.0), [1, 2, 3, 4]) 17 | XCTAssertEqual(result.map(\.1), [2, 3, 4, 1]) 18 | } 19 | 20 | func testCircularPairsSingle() throws { 21 | let array = [1] 22 | let result = Array(array.circularPairs()) 23 | XCTAssertEqual(result.count, 1) 24 | XCTAssertEqual(result.map(\.0), [1]) 25 | XCTAssertEqual(result.map(\.1), [1]) 26 | } 27 | } 28 | 29 | final class WingedEdgeTests: XCTestCase { 30 | func testWingedEdge() throws { 31 | var collection = WingedEdgeCollection() 32 | collection.add(face: [ 33 | SIMD3(0, 0, 0), 34 | SIMD3(1, 0, 0), 35 | SIMD3(1, 1, 0), 36 | ]) 37 | XCTAssertEqual(collection.faces.count, 1) 38 | XCTAssertEqual(collection.vertices.count, 3) 39 | XCTAssertEqual(collection.edges.count, 3) 40 | XCTAssertTrue(collection.isValid) 41 | 42 | let device = MTLCreateSystemDefaultDevice()! 43 | let mesh = try collection.toMesh(device: device) 44 | print(mesh) 45 | } 46 | } 47 | --------------------------------------------------------------------------------