{"id":992,"date":"2022-10-13T15:33:25","date_gmt":"2022-10-13T15:33:25","guid":{"rendered":"http:\/\/geopoesis.com\/?page_id=992"},"modified":"2022-10-31T14:01:58","modified_gmt":"2022-10-31T14:01:58","slug":"coding-metal-api-swiftui-metal-shaders","status":"publish","type":"page","link":"http:\/\/geopoesis.com\/?page_id=992","title":{"rendered":"Coding &#8211; Metal API, SwiftUI, Metal Shaders"},"content":{"rendered":"\n<div class=\"wp-block-cover alignfull\" style=\"min-height:300px;aspect-ratio:unset;\"><span aria-hidden=\"true\" class=\"wp-block-cover__background has-ast-global-color-2-background-color has-background-dim-0 has-background-dim\"><\/span><img loading=\"lazy\" decoding=\"async\" width=\"1280\" height=\"853\" class=\"wp-block-cover__image-background wp-image-585\" alt=\"\" src=\"http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/milky-way-nebula-space-1845068.jpg\" data-object-fit=\"cover\" srcset=\"http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/milky-way-nebula-space-1845068.jpg 1280w, http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/milky-way-nebula-space-1845068-300x200.jpg 300w, http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/milky-way-nebula-space-1845068-1024x682.jpg 1024w, http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/milky-way-nebula-space-1845068-768x512.jpg 768w\" sizes=\"auto, (max-width: 1280px) 100vw, 1280px\" \/><div class=\"wp-block-cover__inner-container is-layout-flow wp-block-cover-is-layout-flow\">\n<h1 class=\"has-text-align-center has-text-color wp-block-heading\" id=\"about\" style=\"color:#00ff00\">Coding &#8211; Metal API, SwiftUI, Metal Shaders<\/h1>\n\n\n\n<p class=\"has-text-color has-small-font-size\" style=\"color:#00ff00\">If, like us, you found it tough to get into Apple\u2019s brilliant Metal API maybe some of the following how-to\u2019s can help you. The first few examples are all about utilising Metal from within the SwiftUI framework (which we think is brilliant by the way). Later we will add example code for pure Metal coding to run on Mac OS.<\/p>\n\n\n\n<p class=\"has-text-color has-small-font-size\" style=\"color:#00ff00\">We broke down various pieces of code into example routines you can adjust into your code. Feel free to use (at your own risk) as you like.<\/p>\n\n\n\n<p class=\"has-text-color has-small-font-size\" style=\"color:#00ff00\">We would be delighted to hear any feedback.&nbsp; So, thanks for reading this page and thanks again, in advance, if you now go to the<a title=\"Contact \u2013 Geopoesis: RTS game : terraform hostile planets\" href=\"http:\/\/geopoesis.com\/?page_id=220\"> Contact page<\/a> and send us comments.&nbsp; We will do our best to answer all emails received.<\/p>\n\n\n\n<ul class=\"has-text-color has-small-font-size wp-block-list\" style=\"color:#00ff00\"><li><a href=\"#1\" data-type=\"internal\" data-id=\"#1\">How to open a Metal View from SwiftUI<\/a><\/li><li><a href=\"#2\" data-type=\"internal\" data-id=\"#2\">Instancing &#8211; draw loads of triangles in a Metal View from SwiftUI<\/a><\/li><li>Simple way to save data from an iOS game if the game is backgrounded to avoid loss of data if iOS decides to terminate it (COMING)<\/li><li>Capture direction of swipe (COMING) and report it into you Metal draw loops<\/li><li>Matrix transformations between views in Metal (COMING)<\/li><li>Interactive particles \u2013 this is very cool and shows just how powerful the most basic phone is if you utilise Metal ! (COMING)<\/li><li>Navigating a 3D universe in Metal (COMING)<\/li><\/ul>\n\n\n\n<p class=\"has-text-color has-small-font-size\" id=\"1\" style=\"color:#00ff00\"><strong>How to open a Metal View from SwiftUI (in a frame of fixed size)<\/strong><\/p>\n\n\n\n<p class=\"has-text-color has-small-font-size\" style=\"color:#00ff00\">This executes from top to bottom and is in skeleton form.&nbsp; It should all work perfectly down to class Renderer: NSObject where you need to set up your Metal queues, libraries, buffers and pipelines and then you need to write the extension to Renderer: MTKViewDelegate so it draws something.&nbsp; But this code will get you to the fun bit.<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:#00ff00;font-size:10px\">import SwiftUI<br>import Metal<br>import MetalKit<br><br>var body: some View { \/\/ Invoke a metal window from swiftUI view<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ZStack() {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ Add your own interfaces here<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SwiftUIView {metalView()}.frame(width: CGFloat(something), height: CGFloat(something))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>}<br><br>public struct SwiftUIView: UIViewRepresentable {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public var wrappedView: UIView<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private var handleUpdateUIView: ((UIView, Context) -&gt; Void)?<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private var handleMakeUIView: ((Context) -&gt; UIView)?<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public init(closure: () -&gt; UIView) { wrappedView = closure() }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public func makeUIView(context: Context) -&gt; UIView {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; guard let handler = handleMakeUIView else { return wrappedView }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return handler(context)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public func updateUIView(_ uiView: UIView, context: Context) { handleUpdateUIView?(uiView, context) }<br>}<br><br>public extension SwiftUIView {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mutating func setMakeUIView(handler: @escaping (Context) -&gt; UIView) -&gt; Self {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handleMakeUIView = handler<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mutating func setUpdateUIView(handler: @escaping (UIView, Context) -&gt; Void) -&gt; Self {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handleUpdateUIView = handler<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>}<br><br>class metalView: MTKView {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var renderer: Renderer!&nbsp; \/\/ Once set up you can init this (see below)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; init() {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.init(frame: .zero, device: MTLCreateSystemDefaultDevice()) \/\/ 2. Get an instance of the GPU<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; colorPixelFormat = .bgra8Unorm<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.framebufferOnly = false<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.preferredFramesPerSecond = 60<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; renderer = Renderer(device: device!, metalView: self)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delegate = renderer \/\/ Go and start the fun stuff in the rendering set-up and loops now the set up is done<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required init(coder: NSCoder) { fatalError(&#8220;init(coder:) has not been implemented&#8221;) }<br>}<br><br>class Renderer: NSObject {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ 3. Set up all queues, libraries, buffers and pipelines<br>}<br><br>extension Renderer: MTKViewDelegate {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ 4. Set some one-offs<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; func draw(in view: MTKView) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ 5. Draw loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>}<\/p>\n\n\n\n<p class=\"has-text-color has-small-font-size\" id=\"2\" style=\"color:#00ff00\"><strong>Instancing &#8211; draw loads of triangles in a Metal View from SwiftUI<\/strong><\/p>\n\n\n\n<p class=\"has-text-color has-small-font-size\" style=\"color:#00ff00\">Ok \u2013 101 \u2013 Hello some triangles (the Metal render functions and support functions) in a Metal View window invoked from within SwiftUI.&nbsp; They are drawn in a reasonably efficient way using rudimentary instancing import SwiftUI<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:#00ff00;font-size:10px\">import Metal<br>import MetalKit<br><br>\/\/ Set up objects (here a simple triangle)<br>struct Vertex {var position: SIMD2&lt;Float&gt;; var color: SIMD4&lt;Float&gt;; var texCord: SIMD2&lt;Float&gt;;}<br>var verticesMatrix = [Vertex(position: SIMD2&lt;Float&gt;(0,1), color: SIMD4&lt;Float&gt;(0,1,1,1), texCord: SIMD2&lt;Float&gt;(0.5, 0)), Vertex(position: SIMD2&lt;Float&gt;(-1,-1), color: SIMD4&lt;Float&gt;(1,0,1,1), texCord: SIMD2&lt;Float&gt;(0, 1)), Vertex(position: SIMD2&lt;Float&gt;(1,-1), color: SIMD4&lt;Float&gt;(1,1,1,1), texCord: SIMD2&lt;Float&gt;(1, 1))]<br>var uniformMatrix = [simd_float4x4]()<br><br>class Renderer: NSObject { \/\/ 3. Set up all queues, libraries, buffers and pipelines<br><br>&nbsp;&nbsp;&nbsp; var queue: MTLCommandQueue!<br>&nbsp;&nbsp;&nbsp; var renderState: MTLRenderPipelineState!<br>&nbsp;&nbsp;&nbsp; var vertexMatrixBuffer: MTLBuffer!<br>&nbsp;&nbsp;&nbsp; var instances: Int!<br><br>&nbsp;&nbsp;&nbsp; init(device: MTLDevice, metalView: MTKView) {<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; queue = device.makeCommandQueue()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; let library = device.makeDefaultLibrary()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; renderState = buildPipelineState(device: device) \/\/ 4. Set comm framework<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vertexMatrixBuffer = device.makeBuffer(bytes: verticesMatrix, length: MemoryLayout&lt;Vertex&gt;.stride * verticesMatrix.count, options: [])<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; instances = 5<br><br>&nbsp;&nbsp;&nbsp; }<br>}<br><br>extension Renderer: MTKViewDelegate {<br>&nbsp; func draw(in view: MTKView) {<br><br>&nbsp;&nbsp;&nbsp; guard let commandBuffer = queue.makeCommandBuffer(), let drawable = view.currentDrawable else { return } \/\/ Set up draw run<br><br>&nbsp;&nbsp;&nbsp; \/\/ Render some meshes<br>&nbsp;&nbsp;&nbsp; guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: view.currentRenderPassDescriptor!) else { return }<br>&nbsp;&nbsp;&nbsp; renderEncoder.setRenderPipelineState(renderState)<br>&nbsp;&nbsp;&nbsp; renderEncoder.setVertexBuffer(vertexMatrixBuffer, offset: 0, index: 0)<br>&nbsp;&nbsp;&nbsp; uniformMatrix.removeAll()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for count in 0 ..&lt; instances {uniformMatrix.append((matrixTransform(transPosition: [Float(count) \/ 20, 0.5, 0,1]) * scalingMatrix(scaleX: 0.1, scaleY: 0.1, scaleZ: 1.0) ))}<br>&nbsp;&nbsp;&nbsp; renderEncoder.setVertexBytes(&amp;uniformMatrix, length: MemoryLayout&lt;simd_float3x3&gt;.size * instances, index: 1)<br>&nbsp;&nbsp;&nbsp; renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: verticesMatrix.count, instanceCount: instances)<br>&nbsp;&nbsp;&nbsp; renderEncoder.endEncoding()<br><br>&nbsp;&nbsp;&nbsp; commandBuffer.present(drawable)<br>&nbsp;&nbsp;&nbsp; commandBuffer.commit()<br>&nbsp; }<br>&nbsp;<br>&nbsp; public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}<br>}<br><br>func buildPipelineState(device: MTLDevice) -&gt; MTLRenderPipelineState {<br><br>&nbsp;&nbsp;&nbsp; let pipeDesc = MTLRenderPipelineDescriptor()<br>&nbsp;&nbsp;&nbsp; pipeDesc.vertexFunction = device.makeDefaultLibrary()?.makeFunction(name: &#8220;baseVertFunc&#8221;) \/\/ 4a. Convert mesh to 2D<br>&nbsp;&nbsp;&nbsp; pipeDesc.fragmentFunction = device.makeDefaultLibrary()?.makeFunction(name: &#8220;baseFragFunc&#8221;) \/\/ 4b. Colour the gaps<br>&nbsp;&nbsp;&nbsp; pipeDesc.colorAttachments[0].pixelFormat = .bgra8Unorm<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp; return try! device.makeRenderPipelineState(descriptor: pipeDesc)<br>&nbsp;&nbsp;&nbsp;<br>}<br><br>import simd \/\/&nbsp; Maths<br><br>func matrixTransform(transPosition: SIMD4&lt;Float&gt;) -&gt; simd_float4x4 {<br>&nbsp;&nbsp;&nbsp; return simd_float4x4(columns:(SIMD4&lt;Float&gt;(1, 0, 0, 0), SIMD4&lt;Float&gt;(0, 1, 0, 0), SIMD4&lt;Float&gt;(0, 0, 0, 0), SIMD4&lt;Float&gt;(transPosition.x, transPosition.y, 0, 1)))<br>}<br><br>func scalingMatrix(scaleX: Float, scaleY: Float, scaleZ: Float) -&gt; simd_float4x4 {<br>&nbsp;&nbsp; &nbsp;return simd_float4x4(columns:(SIMD4&lt;Float&gt;(scaleX, 0, 0, 0), SIMD4&lt;Float&gt;(0, scaleY, 0, 0), SIMD4&lt;Float&gt;(0, 0, scaleZ, 0), SIMD4&lt;Float&gt;(0, 0, 0, 1)))<br>}<br><br>\/\/ You need a super simple shader function<br><br>#include &lt;metal_stdlib&gt;<br>using namespace metal;<br><br>struct VertexIn {float4 position;float4 color;float2 texCord;};<br><br>struct PerInstanceUniforms {float4x4 positioning;};<br><br>struct VertexOut {<br>&nbsp;&nbsp;&nbsp; float4 position [[ position ]];<br>&nbsp;&nbsp;&nbsp; float4 color;<br>&nbsp;&nbsp;&nbsp; float2 texCord;<br>};<br><br>vertex VertexOut baseVertFunc(const device VertexIn *vertices [[ buffer(0) ]], constant PerInstanceUniforms *perInstanceUniforms [[ buffer(1) ]], uint vertexID [[ vertex_id&nbsp; ]], uint iid [[ instance_id ]]) {<br><br>&nbsp;&nbsp;&nbsp; VertexOut vOut;<br>&nbsp;&nbsp;&nbsp; vOut.position = perInstanceUniforms[iid].positioning * float4(vertices[vertexID].position.x, vertices[vertexID].position.y, 0.0, 1);<br>&nbsp;&nbsp;&nbsp; vOut.color = vertices[vertexID].color;<br>&nbsp;&nbsp;&nbsp; vOut.texCord = vertices[vertexID].texCord;<br>&nbsp;&nbsp;&nbsp; return vOut;<br>}<br><br>fragment float4 baseFragFunc(VertexOut vIn [[ stage_in ]]) {<br>&nbsp;&nbsp;&nbsp; return vIn.color;<br>}<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p class=\"has-text-align-center has-ast-global-color-7-color has-text-color\" style=\"font-size:10px\"><a href=\"http:\/\/geopoesis.com\/?page_id=3\" title=\"Privacy Policy\">Privacy<\/a><\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p class=\"has-text-align-center has-ast-global-color-7-color has-text-color\" style=\"font-size:10px\">Copyright \u00a9 2021 geopoesis.com<\/p>\n<\/div>\n<\/div>\n<\/div><\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"om_disable_all_campaigns":false,"site-sidebar-layout":"","site-content-layout":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"disabled","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","footnotes":""},"class_list":["post-992","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v19.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Coding - Metal API, SwiftUI, Metal Shaders - geopoesis.com<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"http:\/\/geopoesis.com\/?page_id=992\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Coding - Metal API, SwiftUI, Metal Shaders - geopoesis.com\" \/>\n<meta property=\"og:url\" content=\"http:\/\/geopoesis.com\/?page_id=992\" \/>\n<meta property=\"og:site_name\" content=\"geopoesis.com\" \/>\n<meta property=\"article:modified_time\" content=\"2022-10-31T14:01:58+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/milky-way-nebula-space-1845068.jpg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"http:\/\/geopoesis.com\/?page_id=992\",\"url\":\"http:\/\/geopoesis.com\/?page_id=992\",\"name\":\"Coding - Metal API, SwiftUI, Metal Shaders - geopoesis.com\",\"isPartOf\":{\"@id\":\"http:\/\/geopoesis.com\/#website\"},\"datePublished\":\"2022-10-13T15:33:25+00:00\",\"dateModified\":\"2022-10-31T14:01:58+00:00\",\"breadcrumb\":{\"@id\":\"http:\/\/geopoesis.com\/?page_id=992#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"http:\/\/geopoesis.com\/?page_id=992\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"http:\/\/geopoesis.com\/?page_id=992#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"http:\/\/geopoesis.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Coding &#8211; Metal API, SwiftUI, Metal Shaders\"}]},{\"@type\":\"WebSite\",\"@id\":\"http:\/\/geopoesis.com\/#website\",\"url\":\"http:\/\/geopoesis.com\/\",\"name\":\"geopoesis.com\",\"description\":\"Geopoesis: RTS terraforming game on-ramp\",\"publisher\":{\"@id\":\"http:\/\/geopoesis.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"http:\/\/geopoesis.com\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"http:\/\/geopoesis.com\/#organization\",\"name\":\"geopoesis.com\",\"url\":\"http:\/\/geopoesis.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"http:\/\/geopoesis.com\/#\/schema\/logo\/image\/\",\"url\":\"http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/Picture-EOS.png\",\"contentUrl\":\"http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/Picture-EOS.png\",\"width\":506,\"height\":832,\"caption\":\"geopoesis.com\"},\"image\":{\"@id\":\"http:\/\/geopoesis.com\/#\/schema\/logo\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Coding - Metal API, SwiftUI, Metal Shaders - geopoesis.com","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"http:\/\/geopoesis.com\/?page_id=992","og_locale":"en_US","og_type":"article","og_title":"Coding - Metal API, SwiftUI, Metal Shaders - geopoesis.com","og_url":"http:\/\/geopoesis.com\/?page_id=992","og_site_name":"geopoesis.com","article_modified_time":"2022-10-31T14:01:58+00:00","og_image":[{"url":"http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/milky-way-nebula-space-1845068.jpg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"http:\/\/geopoesis.com\/?page_id=992","url":"http:\/\/geopoesis.com\/?page_id=992","name":"Coding - Metal API, SwiftUI, Metal Shaders - geopoesis.com","isPartOf":{"@id":"http:\/\/geopoesis.com\/#website"},"datePublished":"2022-10-13T15:33:25+00:00","dateModified":"2022-10-31T14:01:58+00:00","breadcrumb":{"@id":"http:\/\/geopoesis.com\/?page_id=992#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["http:\/\/geopoesis.com\/?page_id=992"]}]},{"@type":"BreadcrumbList","@id":"http:\/\/geopoesis.com\/?page_id=992#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http:\/\/geopoesis.com\/"},{"@type":"ListItem","position":2,"name":"Coding &#8211; Metal API, SwiftUI, Metal Shaders"}]},{"@type":"WebSite","@id":"http:\/\/geopoesis.com\/#website","url":"http:\/\/geopoesis.com\/","name":"geopoesis.com","description":"Geopoesis: RTS terraforming game on-ramp","publisher":{"@id":"http:\/\/geopoesis.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"http:\/\/geopoesis.com\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"http:\/\/geopoesis.com\/#organization","name":"geopoesis.com","url":"http:\/\/geopoesis.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"http:\/\/geopoesis.com\/#\/schema\/logo\/image\/","url":"http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/Picture-EOS.png","contentUrl":"http:\/\/geopoesis.com\/wp-content\/uploads\/2021\/12\/Picture-EOS.png","width":506,"height":832,"caption":"geopoesis.com"},"image":{"@id":"http:\/\/geopoesis.com\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"http:\/\/geopoesis.com\/index.php?rest_route=\/wp\/v2\/pages\/992","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/geopoesis.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"http:\/\/geopoesis.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"http:\/\/geopoesis.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/geopoesis.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=992"}],"version-history":[{"count":11,"href":"http:\/\/geopoesis.com\/index.php?rest_route=\/wp\/v2\/pages\/992\/revisions"}],"predecessor-version":[{"id":1020,"href":"http:\/\/geopoesis.com\/index.php?rest_route=\/wp\/v2\/pages\/992\/revisions\/1020"}],"wp:attachment":[{"href":"http:\/\/geopoesis.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=992"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}