From 6aa00cda17fba2e170301451661e66fe4dcf5395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?furby=E2=84=A2?= Date: Sat, 28 Dec 2024 08:50:48 -0700 Subject: [PATCH] begin usdview+metal implementation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: furby™ --- .gitignore | 1 + Bundler.toml | 12 ++ Package.swift | 3 + .../UsdImaging/UsdImagingGL/Engine.swift | 15 ++ .../UsdImagingGL/RenderParams.swift | 22 ++ Sources/UsdImagingGL/engine.cpp | 10 + .../include/UsdImagingGL/engine.h | 3 + .../UsdView/Hydra/GL/Hydra+GLRenderer.swift | 6 +- .../UsdView/Hydra/Hydra+RenderEngine.swift | 29 ++- Sources/UsdView/Hydra/Hydra+View.swift | 44 ++++ .../Hydra/MTL/Hydra+MTLEnvironment.swift | 200 ++++++++++++++++++ .../UsdView/Hydra/MTL/Hydra+MTLRenderer.swift | 103 ++++----- Sources/UsdView/Hydra/MTL/Hydra+MTLView.swift | 107 ++++++++++ .../Hydra/MTL/MTKView+Environment.swift | 41 ++++ .../UsdView/Hydra/NONE/Hydra+NORenderer.swift | 5 +- .../NONE/Hydra+NOView.swift} | 14 +- .../Resources/Shaders/fragment_main.metal | 7 + .../Resources/Shaders/vertex_main.metal | 19 ++ Sources/UsdView/UsdView+Bundle.swift | 18 ++ Sources/UsdView/UsdView.swift | 17 +- 20 files changed, 574 insertions(+), 102 deletions(-) create mode 100644 Sources/PixarUSD/UsdImaging/UsdImagingGL/RenderParams.swift create mode 100644 Sources/UsdView/Hydra/Hydra+View.swift create mode 100644 Sources/UsdView/Hydra/MTL/Hydra+MTLEnvironment.swift create mode 100644 Sources/UsdView/Hydra/MTL/Hydra+MTLView.swift create mode 100644 Sources/UsdView/Hydra/MTL/MTKView+Environment.swift rename Sources/UsdView/{Protocols/HdRenderEngine.swift => Hydra/NONE/Hydra+NOView.swift} (84%) create mode 100644 Sources/UsdView/Resources/Shaders/fragment_main.metal create mode 100644 Sources/UsdView/Resources/Shaders/vertex_main.metal create mode 100644 Sources/UsdView/UsdView+Bundle.swift diff --git a/.gitignore b/.gitignore index c6898da74..a8193b0d4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ .swiftpm generated target +.index-build diff --git a/Bundler.toml b/Bundler.toml index 6d13902d9..65e3e8df9 100644 --- a/Bundler.toml +++ b/Bundler.toml @@ -1,5 +1,17 @@ format_version = 2 +[projects.UsdView] +source = 'git(https://github.com/wabiverse/SwiftUSD.git)' +revision = 'main' + +[projects.UsdView.products.UsdView] +type = 'executable' + +[projects.UsdView.builder] +name = 'Package.swift' +type = 'wholeProject' +api = 'revision(main)' + [apps.UsdView] identifier = 'foundation.wabi.UsdView' product = 'UsdView' diff --git a/Package.swift b/Package.swift index 3533be944..bfa91fa95 100644 --- a/Package.swift +++ b/Package.swift @@ -1725,6 +1725,9 @@ let package = Package( dependencies: [ .target(name: "PixarUSD"), ], + resources: [ + .process("Resources") + ], cxxSettings: [ .define("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH", .when(platforms: [.windows])), .define("_ALLOW_KEYWORD_MACROS", to: "1", .when(platforms: [.windows])), diff --git a/Sources/PixarUSD/UsdImaging/UsdImagingGL/Engine.swift b/Sources/PixarUSD/UsdImaging/UsdImagingGL/Engine.swift index 0c2b49e2f..55a4726c5 100644 --- a/Sources/PixarUSD/UsdImaging/UsdImagingGL/Engine.swift +++ b/Sources/PixarUSD/UsdImaging/UsdImagingGL/Engine.swift @@ -24,6 +24,11 @@ public extension UsdImagingGL.Engine { + static func createEngine() -> UsdImagingGL.EngineSharedPtr + { + UsdImagingGL.Engine.CreateEngine() + } + static func createEngine(params: Parameters) -> UsdImagingGL.EngineSharedPtr { UsdImagingGL.Engine.CreateEngine(params) @@ -68,6 +73,11 @@ { SetRendererAov(aov.token) } + + func render(root: Usd.Prim, params: UsdImagingGL.RenderParams) + { + Render(root, params) + } } public extension UsdImagingGL.EngineSharedPtr @@ -81,5 +91,10 @@ { pointee.setRenderer(aov: token) } + + func render(root prim: Usd.Prim, params: UsdImagingGL.RenderParams) + { + pointee.render(root: prim, params: params) + } } #endif // canImport(UsdImagingGLEngine) diff --git a/Sources/PixarUSD/UsdImaging/UsdImagingGL/RenderParams.swift b/Sources/PixarUSD/UsdImaging/UsdImagingGL/RenderParams.swift new file mode 100644 index 000000000..746846a9d --- /dev/null +++ b/Sources/PixarUSD/UsdImaging/UsdImagingGL/RenderParams.swift @@ -0,0 +1,22 @@ +/* ---------------------------------------------------------------- + * :: : M E T A V E R S E : :: + * ---------------------------------------------------------------- + * Licensed under the terms set forth in the LICENSE.txt file, this + * file is available at https://openusd.org/license. + * + * Copyright (C) 2016 Pixar. + * Copyright (C) 2024 Wabi Foundation. All Rights Reserved. + * ---------------------------------------------------------------- + * . x x x . o o o . x x x . : : : . o x o . : : : . + * ---------------------------------------------------------------- */ + +#if canImport(UsdImagingGL) + import UsdImagingGL + + public typealias UsdImagingGLRenderParams = Pixar.UsdImagingGLRenderParams + + public extension UsdImagingGL + { + typealias RenderParams = UsdImagingGLRenderParams + } +#endif // canImport(UsdImagingGL) diff --git a/Sources/UsdImagingGL/engine.cpp b/Sources/UsdImagingGL/engine.cpp index 36033e64d..b41dafd2a 100644 --- a/Sources/UsdImagingGL/engine.cpp +++ b/Sources/UsdImagingGL/engine.cpp @@ -183,6 +183,16 @@ UsdImagingGLEngine::UsdImagingGLEngine(const SdfPath &rootPath, } } +// static. +UsdImagingGLEngineSharedPtr UsdImagingGLEngine::CreateEngine() +{ + UsdImagingGLEngineSharedPtr engine = std::make_shared(); + + engine.reset(new UsdImagingGLEngine()); + + return engine; +} + // static. UsdImagingGLEngineSharedPtr UsdImagingGLEngine::CreateEngine(const Parameters ¶ms) { diff --git a/Sources/UsdImagingGL/include/UsdImagingGL/engine.h b/Sources/UsdImagingGL/include/UsdImagingGL/engine.h index 24885fc99..5b335247e 100644 --- a/Sources/UsdImagingGL/include/UsdImagingGL/engine.h +++ b/Sources/UsdImagingGL/include/UsdImagingGL/engine.h @@ -151,6 +151,9 @@ class UsdImagingGLEngine { /// @{ // --------------------------------------------------------------------- + USDIMAGINGGL_API + static UsdImagingGLEngineSharedPtr CreateEngine(); + USDIMAGINGGL_API static UsdImagingGLEngineSharedPtr CreateEngine(const Parameters ¶ms); diff --git a/Sources/UsdView/Hydra/GL/Hydra+GLRenderer.swift b/Sources/UsdView/Hydra/GL/Hydra+GLRenderer.swift index b691b6291..e01f4fac5 100644 --- a/Sources/UsdView/Hydra/GL/Hydra+GLRenderer.swift +++ b/Sources/UsdView/Hydra/GL/Hydra+GLRenderer.swift @@ -24,7 +24,7 @@ import PixarUSD * * The Hydra Engine (``Hd``) OpenGL renderer for the ``UsdView`` * application. */ - class GLRenderer: HdRenderEngine + class GLRenderer { let hgi: Pixar.HgiGLPtr var device: Pixar.HgiGLDevice! @@ -45,7 +45,6 @@ import PixarUSD let driver = HdDriver(name: .renderDriver, driver: hgi.value) #if canImport(UsdImagingGL) - // UsdImagingGL is not available on iOS. engine = UsdImagingGL.Engine.createEngine( rootPath: stage.getPseudoRoot().getPath(), excludedPaths: Sdf.PathVector(), @@ -63,9 +62,6 @@ import PixarUSD { Msg.logger.log(level: .info, "Created HGI -> OpenGL.") } - - public func draw() - {} } } #endif // canImport(HgiGL) diff --git a/Sources/UsdView/Hydra/Hydra+RenderEngine.swift b/Sources/UsdView/Hydra/Hydra+RenderEngine.swift index c8f20809e..b5e22c4f0 100644 --- a/Sources/UsdView/Hydra/Hydra+RenderEngine.swift +++ b/Sources/UsdView/Hydra/Hydra+RenderEngine.swift @@ -15,33 +15,28 @@ import PixarUSD public enum Hydra { - public class RenderEngine: HdRenderEngine + public class RenderEngine { public var stage: UsdStageRefPtr - - public var engine: any HdRenderEngine + public var engine: UsdImagingGL.EngineSharedPtr public required init(stage: UsdStageRefPtr) { self.stage = stage - - #if !os(Linux) && !os(Windows) && !os(Android) && canImport(HgiMetal) && canImport(UsdImagingGL) - engine = Hydra.MTLRenderer(stage: stage) - #elseif canImport(HgiGL) && canImport(UsdImagingGL) - engine = Hydra.GLRenderer(stage: stage) - #else - engine = Hydra.NORenderer(stage: stage) - #endif + engine = UsdImagingGL.Engine.createEngine() } - public func info() + public func render() { - engine.info() - } + var params = UsdImagingGL.RenderParams() + params.frame = Usd.TimeCode.Default() + params.clearColor = .init(0.1, 0.1, 0.1, 1.0) + params.enableIdRender = false + params.showGuides = false + params.showRender = true + params.showProxy = false - public func draw() - { - engine.draw() + engine.render(root: stage.getPseudoRoot(), params: params) } } } diff --git a/Sources/UsdView/Hydra/Hydra+View.swift b/Sources/UsdView/Hydra/Hydra+View.swift new file mode 100644 index 000000000..a7e0d5af1 --- /dev/null +++ b/Sources/UsdView/Hydra/Hydra+View.swift @@ -0,0 +1,44 @@ +/* ---------------------------------------------------------------- + * :: : M E T A V E R S E : :: + * ---------------------------------------------------------------- + * Licensed under the terms set forth in the LICENSE.txt file, this + * file is available at https://openusd.org/license. + * + * Copyright (C) 2016 Pixar. + * Copyright (C) 2024 Wabi Foundation. All Rights Reserved. + * ---------------------------------------------------------------- + * . x x x . o o o . x x x . : : : . o x o . : : : . + * ---------------------------------------------------------------- */ + +import Foundation +import PixarUSD + +#if canImport(Metal) && !os(visionOS) + import Metal + import MetalKit +#endif // canImport(Metal) && !os(visionOS) + +public extension Hydra +{ + #if canImport(Metal) && !os(visionOS) + typealias Viewport = Hydra.MTLView + #elseif os(macOS) + typealias Viewport = NSViewRepresentable + #elseif os(visionOS) || os(tvOS) || os(watchOS) + typealias Viewport = UIViewRepresentable + #else + struct Viewport {} + #endif // canImport(Metal) && !os(visionOS) +} + +public extension Hydra.Viewport +{ + init(engine: Hydra.RenderEngine) + { + #if canImport(Metal) && !os(visionOS) + let device = MTLCreateSystemDefaultDevice()! + let renderer = Hydra.MTLRenderer(device: device)! + self.init(hydra: engine, device: device, renderer: renderer) + #endif // canImport(Metal) && !os(visionOS) + } +} diff --git a/Sources/UsdView/Hydra/MTL/Hydra+MTLEnvironment.swift b/Sources/UsdView/Hydra/MTL/Hydra+MTLEnvironment.swift new file mode 100644 index 000000000..0838f6cf1 --- /dev/null +++ b/Sources/UsdView/Hydra/MTL/Hydra+MTLEnvironment.swift @@ -0,0 +1,200 @@ +/* ---------------------------------------------------------------- + * :: : M E T A V E R S E : :: + * ---------------------------------------------------------------- + * Licensed under the terms set forth in the LICENSE.txt file, this + * file is available at https://openusd.org/license. + * + * Copyright (C) 2016 Pixar. + * Copyright (C) 2024 Wabi Foundation. All Rights Reserved. + * ---------------------------------------------------------------- + * . x x x . o o o . x x x . : : : . o x o . : : : . + * ---------------------------------------------------------------- */ + +import Combine +import MetalKit +import SwiftUI + +public extension Hydra +{ + enum MTLEnvironment + { + struct ColorPixelFormatKey: EnvironmentKey + { + static let defaultValue: MTLPixelFormat = .bgra8Unorm + } + + struct FramebufferOnlyKey: EnvironmentKey + { + static let defaultValue: Bool = true + } + + struct DrawableSizeKey: EnvironmentKey + { + static var defaultValue: CGSize? = nil + } + + struct AutoResizeDrawableKey: EnvironmentKey + { + static var defaultValue: Bool = true + } + + struct ClearColorKey: EnvironmentKey + { + static var defaultValue: MTLClearColor = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0) + } + + struct PreferredFramesPerSecondKey: EnvironmentKey + { + static var defaultValue: Int = 60 + } + + struct IsPausedKey: EnvironmentKey + { + static var defaultValue: Bool = false + } + + struct EnableSetNeedsDisplayKey: EnvironmentKey + { + static var defaultValue: Bool = false + } + + struct PresentWithTransactionKey: EnvironmentKey + { + static var defaultValue: Bool = false + } + + struct SetNeedsDisplayTriggerKey: EnvironmentKey + { + static var defaultValue: Hydra.MTLView.SetNeedsDisplayTrigger? = nil + } + } +} + +extension EnvironmentValues +{ + var colorPixelFormat: MTLPixelFormat + { + get { self[Hydra.MTLEnvironment.ColorPixelFormatKey.self] } + set { self[Hydra.MTLEnvironment.ColorPixelFormatKey.self] = newValue } + } + + var framebufferOnly: Bool + { + get { self[Hydra.MTLEnvironment.FramebufferOnlyKey.self] } + set { self[Hydra.MTLEnvironment.FramebufferOnlyKey.self] = newValue } + } + + var drawableSize: CGSize? + { + get { self[Hydra.MTLEnvironment.DrawableSizeKey.self] } + set { self[Hydra.MTLEnvironment.DrawableSizeKey.self] = newValue } + } + + var autoResizeDrawable: Bool + { + get { self[Hydra.MTLEnvironment.AutoResizeDrawableKey.self] } + set { self[Hydra.MTLEnvironment.AutoResizeDrawableKey.self] = newValue } + } + + var clearColor: MTLClearColor + { + get { self[Hydra.MTLEnvironment.ClearColorKey.self] } + set { self[Hydra.MTLEnvironment.ClearColorKey.self] = newValue } + } + + var preferredFramesPerSecond: Int + { + get { self[Hydra.MTLEnvironment.PreferredFramesPerSecondKey.self] } + set { self[Hydra.MTLEnvironment.PreferredFramesPerSecondKey.self] = newValue } + } + + var enableSetNeedsDisplay: Bool + { + get { self[Hydra.MTLEnvironment.EnableSetNeedsDisplayKey.self] } + set { self[Hydra.MTLEnvironment.EnableSetNeedsDisplayKey.self] = newValue } + } + + var isPaused: Bool + { + get { self[Hydra.MTLEnvironment.IsPausedKey.self] } + set { self[Hydra.MTLEnvironment.IsPausedKey.self] = newValue } + } + + var presentWithTransaction: Bool + { + get { self[Hydra.MTLEnvironment.PresentWithTransactionKey.self] } + set { self[Hydra.MTLEnvironment.PresentWithTransactionKey.self] = newValue } + } + + var setNeedsDisplayTrigger: Hydra.MTLView.SetNeedsDisplayTrigger? + { + get { self[Hydra.MTLEnvironment.SetNeedsDisplayTriggerKey.self] } + set { self[Hydra.MTLEnvironment.SetNeedsDisplayTriggerKey.self] = newValue } + } +} + +public extension View +{ + func colorPixelFormat(_ value: MTLPixelFormat) -> some View + { + environment(\.colorPixelFormat, value) + } + + func framebufferOnly(_ value: Bool) -> some View + { + environment(\.framebufferOnly, value) + } + + func drawableSize(_ value: CGSize?) -> some View + { + environment(\.drawableSize, value) + } + + func autoResizeDrawable(_ value: Bool) -> some View + { + environment(\.autoResizeDrawable, value) + } + + func clearColor(_ value: MTLClearColor) -> some View + { + environment(\.clearColor, value) + } + + func preferredFramesPerSecond(_ value: Int) -> some View + { + environment(\.preferredFramesPerSecond, value) + } + + func isPaused(_ value: Bool) -> some View + { + environment(\.isPaused, value) + } + + func enableSetNeedsDisplay(_ value: Bool) -> some View + { + environment(\.enableSetNeedsDisplay, value) + } + + func presentWithTransaction(_ value: Bool) -> some View + { + environment(\.presentWithTransaction, value) + } + + func setNeedsDisplayTrigger(_ value: Hydra.MTLView.SetNeedsDisplayTrigger?) -> some View + { + environment(\.setNeedsDisplayTrigger, value) + } + + @ViewBuilder + func drawingMode(_ value: Hydra.MTLView.DrawingMode) -> some View + { + switch value + { + case let .timeUpdates(preferredFramesPerSecond): + isPaused(false).enableSetNeedsDisplay(false).preferredFramesPerSecond(preferredFramesPerSecond) + + case let .drawNotifications(setNeedsDisplayTrigger): + isPaused(true).enableSetNeedsDisplay(true).setNeedsDisplayTrigger(setNeedsDisplayTrigger) + } + } +} diff --git a/Sources/UsdView/Hydra/MTL/Hydra+MTLRenderer.swift b/Sources/UsdView/Hydra/MTL/Hydra+MTLRenderer.swift index 17e931b2d..b41ff1195 100644 --- a/Sources/UsdView/Hydra/MTL/Hydra+MTLRenderer.swift +++ b/Sources/UsdView/Hydra/MTL/Hydra+MTLRenderer.swift @@ -18,78 +18,69 @@ import PixarUSD public extension Hydra { - /** - * ``MTLRenderer`` - * - * ## Overview - * - * The Hydra Engine (``Hd``) Metal renderer for the ``UsdView`` - * application conforms to the ``MTKViewDelegate`` protocol, - * allowing it to be set as a ``MTKView`` object's delegate to - * provide a drawing method to a ``MTKView`` object and respond - * to rendering events. */ - class MTLRenderer: NSObject, MTKViewDelegate, HdRenderEngine + class MTLRenderer: NSObject, MTKViewDelegate { - let hgi: Pixar.HgiMetalPtr - var device: MTLDevice! + private let device: MTLDevice + private let commandQueue: MTLCommandQueue + private var pipelineState: MTLRenderPipelineState? - public var stage: UsdStageRefPtr - - #if canImport(UsdImagingGL) - /// UsdImagingGL is not available on iOS. - var engine: UsdImagingGL.EngineSharedPtr - #endif // canImport(UsdImagingGL) - - public init(metalView: MTKView) + init?(device: MTLDevice) { - hgi = HgiMetal.createHgi() - device = hgi.device - stage = Usd.Stage.createInMemory() + self.device = device - let driver = HdDriver(name: .renderDriver, driver: hgi.value) + guard let commandQueue = device.makeCommandQueue() + else { return nil } - #if canImport(UsdImagingGL) - // UsdImagingGL is not available on iOS. - engine = UsdImagingGL.Engine.createEngine( - rootPath: stage.getPseudoRoot().getPath(), - excludedPaths: Sdf.PathVector(), - invisedPaths: Sdf.PathVector(), - sceneDelegateId: Sdf.Path.absoluteRootPath(), - driver: driver - ) - #endif // canImport(UsdImagingGL) + self.commandQueue = commandQueue + super.init() - metalView.device = hgi.device + setupPipeline() } - public required convenience init(stage: UsdStageRefPtr) + private func setupPipeline() { - self.init(metalView: MTKView()) - self.stage = stage + let library = try! device.makeDefaultLibrary(bundle: .usdview) + let vertexFunction = library.makeFunction(name: "vertex_main") + let fragmentFunction = library.makeFunction(name: "fragment_main") - #if canImport(UsdImagingGL) - // UsdImagingGL is not available on iOS. - engine.setEnablePresentation(false) - engine.setRenderer(aov: .color) - #endif // canImport(UsdImagingGL) - } + let pipelineDescriptor = MTLRenderPipelineDescriptor() + pipelineDescriptor.vertexFunction = vertexFunction + pipelineDescriptor.fragmentFunction = fragmentFunction + pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm - public func info() - { - Msg.logger.log(level: .info, "Created HGI -> Metal API v\(hgi.apiVersion).") - Msg.logger.log(level: .info, "GPU Architecture -> \(hgi.device.architecture.name)") + pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDescriptor) } - public func mtkView(_: MTKView, drawableSizeWillChange size: CGSize) + public func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) + {} + + public func draw(in view: MTKView) { - print("drawableSizeWillChange", size) - } + guard + let drawable = view.currentDrawable, + let renderPassDescriptor = view.currentRenderPassDescriptor, + let pipelineState + else { return } - public func draw() - {} + let commandBuffer = commandQueue.makeCommandBuffer() + let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor) - public func draw(in _: MTKView) - {} + renderEncoder?.setRenderPipelineState(pipelineState) + renderEncoder?.setViewport( + MTLViewport( + originX: 0.0, + originY: 0.0, + width: Double(view.drawableSize.width), + height: Double(view.drawableSize.height), + znear: -1.0, + zfar: 1.0 + ) + ) + renderEncoder?.endEncoding() + + commandBuffer?.present(drawable) + commandBuffer?.commit() + } } } #endif // canImport(Metal) && !os(visionOS) diff --git a/Sources/UsdView/Hydra/MTL/Hydra+MTLView.swift b/Sources/UsdView/Hydra/MTL/Hydra+MTLView.swift new file mode 100644 index 000000000..5d0f4b9fb --- /dev/null +++ b/Sources/UsdView/Hydra/MTL/Hydra+MTLView.swift @@ -0,0 +1,107 @@ +/* ---------------------------------------------------------------- + * :: : M E T A V E R S E : :: + * ---------------------------------------------------------------- + * Licensed under the terms set forth in the LICENSE.txt file, this + * file is available at https://openusd.org/license. + * + * Copyright (C) 2016 Pixar. + * Copyright (C) 2024 Wabi Foundation. All Rights Reserved. + * ---------------------------------------------------------------- + * . x x x . o o o . x x x . : : : . o x o . : : : . + * ---------------------------------------------------------------- */ + +import Foundation +import PixarUSD + +#if canImport(SwiftUI) + import Combine + import SwiftUI + + #if canImport(Metal) && !os(visionOS) + import Metal + import MetalKit + + public extension Hydra + { + struct MTLView: NSViewRepresentable + { + public typealias NSViewType = MTKView + public typealias SetNeedsDisplayTrigger = AnyPublisher + + public enum DrawingMode + { + case timeUpdates(preferredFramesPerSecond: Int) + case drawNotifications(setNeedsDisplayTrigger: SetNeedsDisplayTrigger?) + } + + private let hydra: Hydra.RenderEngine! + private let device: MTLDevice! + private let renderer: MTLRenderer! + + public init(hydra: Hydra.RenderEngine, device: MTLDevice, renderer: MTLRenderer) + { + self.hydra = hydra + self.device = device + self.renderer = renderer + } + + public func makeCoordinator() -> Coordinator + { + Coordinator() + } + + public func makeNSView(context: Context) -> MTKView + { + let metalView = context.coordinator.metalView + metalView.device = device + metalView.delegate = renderer + metalView.apply(context.environment) + + context.coordinator.setNeedsDisplayTrigger = context.environment.setNeedsDisplayTrigger + + return metalView + } + + public func updateNSView(_: MTKView, context: Context) + { + context.coordinator.metalView.apply(context.environment) + context.coordinator.setNeedsDisplayTrigger = context.environment.setNeedsDisplayTrigger + + renderer.draw(in: context.coordinator.metalView) + hydra.render() + + print("UPDATE VIEW") + } + + public class Coordinator + { + private var cancellable: AnyCancellable? + public var metalView: MTKView = .init(frame: .zero) + + public init() + { + cancellable = nil + setNeedsDisplayTrigger = nil + } + + public var setNeedsDisplayTrigger: SetNeedsDisplayTrigger? + { + didSet + { + cancellable = setNeedsDisplayTrigger?.receive(on: DispatchQueue.main).sink + { [weak self] in + guard + let self, + metalView.isPaused, + metalView.enableSetNeedsDisplay + else { return } + + metalView.setNeedsDisplay(metalView.bounds) + } + } + } + } + } + } + #endif // canImport(Metal) +#endif // canImport(SwiftUI) diff --git a/Sources/UsdView/Hydra/MTL/MTKView+Environment.swift b/Sources/UsdView/Hydra/MTL/MTKView+Environment.swift new file mode 100644 index 000000000..1b3ac4702 --- /dev/null +++ b/Sources/UsdView/Hydra/MTL/MTKView+Environment.swift @@ -0,0 +1,41 @@ +/* ---------------------------------------------------------------- + * :: : M E T A V E R S E : :: + * ---------------------------------------------------------------- + * Licensed under the terms set forth in the LICENSE.txt file, this + * file is available at https://openusd.org/license. + * + * Copyright (C) 2016 Pixar. + * Copyright (C) 2024 Wabi Foundation. All Rights Reserved. + * ---------------------------------------------------------------- + * . x x x . o o o . x x x . : : : . o x o . : : : . + * ---------------------------------------------------------------- */ + +#if canImport(SwiftUI) + import Combine + import SwiftUI + #if canImport(Metal) && !os(visionOS) + import MetalKit + + extension MTKView + { + @discardableResult + func apply(_ environment: EnvironmentValues) -> Self + { + colorPixelFormat = environment.colorPixelFormat + framebufferOnly = environment.framebufferOnly + if let drawableSize = environment.drawableSize + { + self.drawableSize = drawableSize + } + autoResizeDrawable = environment.autoResizeDrawable + clearColor = environment.clearColor + preferredFramesPerSecond = environment.preferredFramesPerSecond + enableSetNeedsDisplay = environment.enableSetNeedsDisplay + isPaused = environment.isPaused + presentsWithTransaction = environment.presentWithTransaction + + return self + } + } + #endif // canImport(Metal) && !os(visionOS) +#endif // canImport(SwiftUI) diff --git a/Sources/UsdView/Hydra/NONE/Hydra+NORenderer.swift b/Sources/UsdView/Hydra/NONE/Hydra+NORenderer.swift index 60310e254..06a032260 100644 --- a/Sources/UsdView/Hydra/NONE/Hydra+NORenderer.swift +++ b/Sources/UsdView/Hydra/NONE/Hydra+NORenderer.swift @@ -22,7 +22,7 @@ public extension Hydra * * The Hydra Engine (``Hd``) no-op renderer for the ``UsdView`` * application. Note: This renders nothing. */ - class NORenderer: HdRenderEngine + class NORenderer { public var stage: UsdStageRefPtr @@ -35,8 +35,5 @@ public extension Hydra { Msg.logger.log(level: .info, "Created HGI -> None.") } - - public func draw() - {} } } diff --git a/Sources/UsdView/Protocols/HdRenderEngine.swift b/Sources/UsdView/Hydra/NONE/Hydra+NOView.swift similarity index 84% rename from Sources/UsdView/Protocols/HdRenderEngine.swift rename to Sources/UsdView/Hydra/NONE/Hydra+NOView.swift index 7106b40e0..324227cd9 100644 --- a/Sources/UsdView/Protocols/HdRenderEngine.swift +++ b/Sources/UsdView/Hydra/NONE/Hydra+NOView.swift @@ -13,13 +13,11 @@ import Foundation import PixarUSD -public protocol HdRenderEngine +public extension Hydra { - var stage: UsdStageRefPtr { get set } - - init(stage: UsdStageRefPtr) - - func info() - - func draw() + struct NOView + { + public init() + {} + } } diff --git a/Sources/UsdView/Resources/Shaders/fragment_main.metal b/Sources/UsdView/Resources/Shaders/fragment_main.metal new file mode 100644 index 000000000..0f547f5b4 --- /dev/null +++ b/Sources/UsdView/Resources/Shaders/fragment_main.metal @@ -0,0 +1,7 @@ +#include +using namespace metal; + +fragment float4 fragment_main() +{ + return float4(0.1, 0.2, 0.8, 1.0); // Background color +} diff --git a/Sources/UsdView/Resources/Shaders/vertex_main.metal b/Sources/UsdView/Resources/Shaders/vertex_main.metal new file mode 100644 index 000000000..47ae7e438 --- /dev/null +++ b/Sources/UsdView/Resources/Shaders/vertex_main.metal @@ -0,0 +1,19 @@ +#include +using namespace metal; + +struct VertexIn +{ + float4 position [[attribute(0)]]; +}; + +struct VertexOut +{ + float4 position [[position]]; +}; + +vertex VertexOut vertex_main(VertexIn in [[stage_in]]) +{ + VertexOut out; + out.position = in.position; + return out; +} diff --git a/Sources/UsdView/UsdView+Bundle.swift b/Sources/UsdView/UsdView+Bundle.swift new file mode 100644 index 000000000..94861cf88 --- /dev/null +++ b/Sources/UsdView/UsdView+Bundle.swift @@ -0,0 +1,18 @@ +/* ---------------------------------------------------------------- + * :: : M E T A V E R S E : :: + * ---------------------------------------------------------------- + * Licensed under the terms set forth in the LICENSE.txt file, this + * file is available at https://openusd.org/license. + * + * Copyright (C) 2016 Pixar. + * Copyright (C) 2024 Wabi Foundation. All Rights Reserved. + * ---------------------------------------------------------------- + * . x x x . o o o . x x x . : : : . o x o . : : : . + * ---------------------------------------------------------------- */ + +import Foundation + +public extension Bundle +{ + static let usdview = Bundle.module +} diff --git a/Sources/UsdView/UsdView.swift b/Sources/UsdView/UsdView.swift index 2fbe89e4f..bc09a15bc 100644 --- a/Sources/UsdView/UsdView.swift +++ b/Sources/UsdView/UsdView.swift @@ -36,7 +36,7 @@ struct UsdView: PixarApp /// the active usd stage. let stage: UsdStageRefPtr /// the hydra rendering engine. - let hydra: Hydra.RenderEngine + let engine: Hydra.RenderEngine public init() { @@ -47,7 +47,7 @@ struct UsdView: PixarApp stage = Usd.Stage.createNew("\(documentsDirPath())/HelloPixarUSD", ext: .usda) // setup hydra to render the usd stage. - hydra = Hydra.RenderEngine(stage: stage) + engine = Hydra.RenderEngine(stage: stage) runDemo() } @@ -59,9 +59,8 @@ struct UsdView: PixarApp { VStack { - Text("UsdView Under Construction...") - .font(.system(size: 24, weight: .black)) - .padding() + Hydra.Viewport(engine: engine) + .frame(maxWidth: .infinity, maxHeight: .infinity) } } } @@ -74,18 +73,12 @@ struct UsdView: PixarApp func runDemo() { - // show hydra gpu info. - hydra.info() - // create usd scene. createScene() - // declarative usd scene - // (using swiftui-like api). + // declarative usd scene. declareScene() Msg.logger.log(level: .info, "UsdView launched | USD v\(Pixar.version).") - - // complete. } }