Skip to content

Commit

Permalink
Add metal blitting with hgi texture.
Browse files Browse the repository at this point in the history
Signed-off-by: furby™ <[email protected]>
  • Loading branch information
furby-tm committed Dec 29, 2024
1 parent d396ec7 commit 9126efe
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 32 deletions.
2 changes: 2 additions & 0 deletions Sources/PixarUSD/Usd/Usd/Attribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ public extension Usd.Attribute
SetDocumentation(std.string(doc))
}

@discardableResult
func set(_ value: String, time: UsdTimeCode = UsdTimeCode.Default()) -> Bool
{
Set(std.string(value), time)
}

@discardableResult
func set(_ value: Sdf.AssetPath, time: UsdTimeCode = UsdTimeCode.Default()) -> Bool
{
Set(value, time)
Expand Down
2 changes: 1 addition & 1 deletion Sources/UsdImagingGL/include/UsdImagingGL/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ class UsdImagingGLEngine {

/// Returns an AOV texture handle for the given token.
USDIMAGINGGL_API
HgiTextureHandle GetAovTexture(TfToken const &name) const;
HgiTextureHandle GetAovTexture(TfToken const &name) const SWIFT_RETURNS_INDEPENDENT_VALUE;

/// Returns the AOV render buffer for the given token.
USDIMAGINGGL_API
Expand Down
39 changes: 35 additions & 4 deletions Sources/UsdView/Hydra/Hydra+RenderEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,54 @@ public enum Hydra
)
}

public func render(rgba: (Double, Double, Double, Double))
public func render(at timeCode: Double, viewSize: CGSize) -> Pixar.HgiTextureHandle
{
var params = UsdImagingGL.RenderParams()
// Draws the scene using Hydra.
// Camera projection setup.
// let cameraTransform = viewCamera.getTransform()
// let cameraParams = viewCamera.getShaderParams()
// let frustum = computeFrustum(cameraTransform: cameraTransform, viewSize: viewSize, params: cameraParams)
// let modelViewMatrix = frustum.computeViewMatrix()
// let projMatrix = frustum.computeProjectionMatrix()
// engine.setCameraState(modelViewMatrix: modelViewMatrix, projMatrix: projMatrix)

// Viewport setup.
let viewport = Gf.Vec4d(0, 0, viewSize.width, viewSize.height)
engine.pointee.SetRenderViewport(viewport)
engine.pointee.SetWindowPolicy(Pixar.CameraUtilConformWindowPolicy.init(0))

params.frame = Usd.TimeCode.Default()
params.clearColor = .init(Float(rgba.0), Float(rgba.1), Float(rgba.2), Float(rgba.3))
var params = UsdImagingGL.RenderParams()
params.frame = Usd.TimeCode(timeCode)
params.clearColor = .init(Float(0.2), Float(0.2), Float(0.2), Float(1.0))
params.enableIdRender = false
params.showGuides = true
params.showRender = true
params.showProxy = true

// Light and material setup.
// let lights = computeLights(cameraTransform: cameraTransform)
// engine.setLightingState(lights: lights, material: material, sceneAmbient: sceneAmbient)

// Render the frame.
engine.render(root: stage.getPseudoRoot(), params: params)

// Return the color output.
return engine.pointee.GetAovTexture(Hd.AovTokens.color.token)
}

public var hydraDevice: MTLDevice
{
hgi.device
}

public func getHgi() -> Pixar.HgiMetalPtr
{
hgi
}

public func getEngine() -> UsdImagingGL.EngineSharedPtr
{
engine
}
}
}
143 changes: 116 additions & 27 deletions Sources/UsdView/Hydra/MTL/Hydra+MTLRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import PixarUSD
{
private let device: MTLDevice
private var hydra: Hydra.RenderEngine?
//private let commandQueue: MTLCommandQueue
/// private let commandQueue: MTLCommandQueue
private var pipelineState: MTLRenderPipelineState?

private var inFlightSemaphore = DispatchSemaphore(value: 1)

convenience init(device: MTLDevice, hydra: Hydra.RenderEngine)
{
self.init(device: device)!
Expand All @@ -41,7 +43,7 @@ import PixarUSD
// self.commandQueue = commandQueue
super.init()

setupPipeline()
loadMetal()
}

private func setupPipeline()
Expand All @@ -63,33 +65,120 @@ import PixarUSD

public func draw(in view: MTKView)
{
guard let drawable = view.currentDrawable else { return }
guard let renderPassDescriptor = view.currentRenderPassDescriptor else { return }

let commandQueue = view.device?.makeCommandQueue()
let commandBuffer = commandQueue?.makeCommandBuffer()
let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor)

if let pipelineState {
renderEncoder?.setRenderPipelineState(pipelineState)
drawFrame(in: view, timeCode: 0.0)
}

/// draw the scene, and blit the result to the view.
func drawFrame(in view: MTKView, timeCode: Double) -> Bool
{
guard let hgi = hydra?.getHgi() else { return false }

// start the next frame.
_ = inFlightSemaphore.wait(timeout: .distantFuture)
defer { inFlightSemaphore.signal() }

hgi.pointee.StartFrame()

// draw the scene using Hydra, and recast the result to a MTLTexture.
let viewSize = view.drawableSize
guard
let hgiTexture = hydra?.render(at: timeCode, viewSize: viewSize),
let metalTexture = hgiTexture.GetId() as? MTLTexture,
let commandBuffer = hgi.pointee.GetPrimaryCommandBuffer()
else { return false }

// copy the rendered texture to the view.
blitToView(view, commandBuffer: commandBuffer, texture: metalTexture)

// tell Hydra to commit the command buffer and complete the work.
hgi.pointee.CommitPrimaryCommandBuffer()
hgi.pointee.EndFrame()

return true
}

/// copies the texture to the view with a shader.
public func blitToView(_ view: MTKView, commandBuffer: MTLCommandBuffer, texture: MTLTexture)
{
guard let renderPassDescriptor = view.currentRenderPassDescriptor
else
{
return
}

// Create a render command encoder to encode the copy command.
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
else
{
return
}

renderEncoder?.setViewport(
MTLViewport(
originX: 0.0,
originY: 0.0,
width: Double(view.drawableSize.width),
height: Double(view.drawableSize.height),
znear: 0.0,
zfar: 1.0
)
)

hydra?.render(rgba: (0.1, 0.1, 0.1, 1.0))

renderEncoder?.endEncoding()
commandBuffer?.present(drawable)
commandBuffer?.commit()
// Blit the texture to the view.
renderEncoder.pushDebugGroup("FinalBlit")
renderEncoder.setFragmentTexture(texture, index: 0)
if let pipelineState
{
renderEncoder.setRenderPipelineState(pipelineState)
}
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
renderEncoder.popDebugGroup()

// Finish encoding the copy command.
renderEncoder.endEncoding()
if let drawable = view.currentDrawable
{
commandBuffer.present(drawable)
}
}

/// Prepares the Metal objects for copying to the view.
private func loadMetal()
{
do
{
let defaultLibrary = try device.makeDefaultLibrary(bundle: .usdview)

guard let vertexFunction = defaultLibrary.makeFunction(name: "vtxBlit")
else
{
print("Failed to create vertex function.")
return
}

guard let fragmentFunction = defaultLibrary.makeFunction(name: "fragBlitLinear")
else
{
print("Failed to create fragment function.")
return
}

// Set up the pipeline state descriptor.
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.rasterSampleCount = 1
pipelineStateDescriptor.vertexFunction = vertexFunction
pipelineStateDescriptor.fragmentFunction = fragmentFunction
pipelineStateDescriptor.depthAttachmentPixelFormat = .invalid

// Configure the color attachment for blending.
if let colorAttachment = pipelineStateDescriptor.colorAttachments[0]
{
colorAttachment.pixelFormat = .bgra8Unorm
colorAttachment.isBlendingEnabled = true
colorAttachment.rgbBlendOperation = .add
colorAttachment.alphaBlendOperation = .add
colorAttachment.sourceRGBBlendFactor = .one
colorAttachment.sourceAlphaBlendFactor = .one
colorAttachment.destinationRGBBlendFactor = .oneMinusSourceAlpha
colorAttachment.destinationAlphaBlendFactor = .zero
}

// Create the pipeline state object.
pipelineState = try device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
}
catch
{
print("Failed to create pipeline state: \(error.localizedDescription)")
}
}
}
}
Expand Down
56 changes: 56 additions & 0 deletions Sources/UsdView/Resources/Shaders/BlitShaders.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Copyright © 2022 Apple Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <metal_stdlib>
using namespace metal;

struct VertexOut
{
float4 position [[ position ]];
float2 texcoord;
};

vertex VertexOut vtxBlit(uint vid [[vertex_id]])
{
// These vertices map a triangle to cover a fullscreen quad.
const float4 vertices[] = {
float4(-1, -1, 1, 1), // bottom left
float4(3, -1, 1, 1), // bottom right
float4(-1, 3, 1, 1), // upper left
};

const float2 texcoords[] = {
float2(0.0, 0.0), // bottom left
float2(2.0, 0.0), // bottom right
float2(0.0, 2.0), // upper left
};

VertexOut out;
out.position = vertices[vid];
out.texcoord = texcoords[vid];
return out;
}

fragment half4 fragBlitLinear(VertexOut in [[stage_in]], texture2d<float> tex[[texture(0)]])
{
constexpr sampler s = sampler(address::clamp_to_edge);

float4 pixel = tex.sample(s, in.texcoord);
return half4(pixel);
}

0 comments on commit 9126efe

Please sign in to comment.