Are parallel views possible

I’m far from finished the book but scanning ahead it doesn’t seem to address my following question:

If I have a mesh and want to view it from several perspectives simultaneously (in separate views), it would seem that I need several renderers, each referring to a different vertex shader. I can imagine how I would do this but I’m confused as to what would be going on.

Wouldn’t I have multiples pipelines, each ignorant of the others existence, and competing for the GPU, loading and reloading the same vertex buffers and hampering each others ability to produce 60 frames per second?

Or should I use a single renderer and the argument in draw(in view: MTKView) to look at which view is calling and choose which shader to apply, leaving all the buffers undisturbed?

Going further, suppose I have two meshes, utterly independent of each other, that I wish to show simultaneously in two views, am I back in the competition described above, or can I have them share the GPU using separate buffers? Am I using a single renderer or one for each?

If there are multiple renderers, each loading vertex data to differently numbered buffers, will the data survive there without reloading?

If these questions are mad, just say so :upside_down_face:

I would suggest different viewports rather than different views. Even for drawing independent meshes, you can still use the same render command encoder.

This is a naive implementation of chapter 11’s cube in two separate viewports:

Materials.zip (1.1 MB)

The view is divided in half with the identical scene models being rendered from two different cameras. There’s only one render command encoder, and it uses the same pipeline state object.

There is redundant texture and vertex binding in the second viewport, so you’d have to reorganise the code in that respect, so that the binding of buffers takes place away from the actual drawing of the scene.

A viewport is a section of the drawable that you draw into. It’s different from a scissor rect (described in chapter 20), in that a scissor rect is clipping the drawing in the viewport, whereas a viewport is like a scaled view. The view’s drawable texture is still the same size as the view, but you can have multiple viewports that draw into that drawable texture.

1 Like

Thanks Caroline, I’ll look into it.
I was using the question to get a better idea of what’s actually going on down in the GPU and I still don’t get it.
I think the buffers are in memory accessible to both CPU and GPU and I guess still retained as long as the renderer instance is alive. A second renderer could repeat reference to buffers 0,1,2 … and these would in separate pipelines so the GPU would not confuse the numbers.
But I wondered, can the GPU even talk to the first renderer after the second one was instantiated and tore down the first pipeline?

I don’t even know if I’m making sense !?!?!

This doesn’t sound right. You’d be scheduling work on the GPU haphazardly. But I honestly don’t know whether you’d get away with it unless your GPU rendering was light.

Using that project I recently uploaded, I added a VStack to ContentView and duplicated MetalView. MetalView creates its own GameController, which initializes its own Renderer, so there are two views with different delegate instances. I also made the mesh a class property on Model, so that the same mesh was being rendered in both views, and this was the result.

Note that by doing this,

  1. You’d have two command buffers, which is not best practice

  2. Even if the resources are resident, you’d have duplicate render command encoders, so you’d be setting pipeline states and duplicating the setVertexBuffer commands.

It’s my understanding that the resource buffer will stay resident (but not addressable unless set up by a render command encoder command) unless it’s changed, or if there is a lack of memory.

Hi,
I’ve played about with this quite a bit since we discussed it and viewports are very much a useful solution to multiple cameras watching the same object. Very elegant! Here are four cameras working simultaneously.

I’m still wrestling with struct MetalView however. If we take struct Button for example, it is a reusable struct and we provide content to customise it to our particular use case. I feel struct MetalView should be similar so I’ve been experimenting with init(content: Renders). I still have a major question mark over whether it makes sense to have more than one MetalView but putting that aside for the moment, I’ve encountered one more problem.
My struct is essentially the same as yours but omits

   @State private var metalView = MTKView()

which it can pick up from renderer. Here it is:

import SwiftUI
import MetalKit

struct MetalView: View {
   @State var renderer: Renderer

   var body: some View {
      MetalViewRepresentable(metalView: $renderer.metalView)
   }
}

#if os(macOS)
struct MetalViewRepresentable: NSViewRepresentable {
   @Binding var metalView: MTKView

   func makeNSView(context: Context) -> some NSView {
      return metalView
   }

   func updateNSView(_ uiView: NSViewType, context: Context) { }
}
#endif

#if os(iOS)
struct MetalViewRepresentable: UIViewRepresentable {
   @Binding var metalView: MTKView

   func makeUIView(context: Context) -> MTKView {
      metalView
   }

   func updateUIView(_ uiView: MTKView, context: Context) { }
}
#endif

struct MetalView_Previews: PreviewProvider {
   static var previews: some View {
      MetalView(renderer: Renderer())
   }
}

It works perfectly well, but note that my @State variable cannot be marked private (as is recommended) because MetalView(renderer:) becomes inaccessible. I feel I must be misusing @State but I can’t see why.

P.S I did try substituting:

   @StateObject var renderer: Renders

and adding:

    protocol Renders: ObservableObject {
      var metalView: MTKView { get set }
   }

and making class Renderer conform to ObservableObject and Renders with no obvious adverse consequences but I don’t know if that is correct practice.
Also, curiously, I can’t enforce conformance to NSObject at the level of the protocol!

The viewports look nice :slight_smile:

SwiftUI and property wrappers are a whole new ballpark!

You shouldn’t ever refer to State properties outside of the structure, so they are always marked as private. State properties live and die with the structure. When a View structure needs refreshing because the data it shows has changed, any State properties will be reinitialised.

When writing data transfer for SwiftUI (or any well-designed app for that matter), there’s one important catchphrase: “One source of truth”. So you place your source of truth high up, and pass it down using Bindings or the Environment (or lets if the data is constant).

Chapter 20, Fragment Post-Processing has a few checkboxes that are passed to the Renderer. In ContentView, options is initialised as a StateObject, which is guaranteed not go away when ContentView is refreshed. options are passed to OptionsView as Bindings (as this is where the user changes them) and to MetalView via a let.

I don’t know if that solves your problem. Of course Apple have just changed how SwiftUI data flow works (in the name of simplifying it), and I haven’t yet come up to speed with it.

There’s a session on the new Observable: Discover Observation in SwiftUI - WWDC23 - Videos - Apple Developer

It’s difficult to program when one doesn’t have a decent mental model of what the machine is doing.

The following just occurred to me today. I was running one of my Metal projects from inside Xcode. It produces a simple animated object in a view. At the same time, my SwiftUI preview in Xcode was animating a very similar view.

Therefore, I thought, my computer is sharing the GPU between two processes. It makes me wonder. I still don’t know exactly what’s going on but the GPU is handling two distinct apps either simultaneously or alternately. I presume it doesn’t have to do all the pipeline setup every time it switches between processing views. I’m guessing that just as the CPU switches between processes, the GPU has a similar mechanism. (This is a different case from the one in my question above where I was considering two views inside the same app).

Anyhow, I’m just thinking out loud here :slight_smile: