Replies: 1 comment
-
UltraviolenceA declarative framework for Metal rendering in Swift. Problem StatementMetal is an incredibly powerful API but has a reputation for being challenging to work with. This is mainly because it is a low-level API requiring heaps of boilerplate code to get something basic up and running. On the other hand, frameworks like SceneKit and RealityKit offer high-level abstractions that simplify 3D rendering but can be limiting when you need more control. Ultraviolence (just a placeholder name for now) will aim to strike a balance between these extremes. It will provide a declarative, SwiftUI-inspired API that will be easy to use while giving you low-level control when needed. Time to First TeapotIf you have ever looked at Apple’s Metal sample code or used the Metal templates in Xcode, you will probably have noticed that about 95% of the code will be boilerplate every Metal app needs just to get started. Ultraviolence will aim to cut out most, if not all, of that boilerplate so you can focus on what will really matter: your rendering code. Possible example: import SwiftUI
import Ultraviolence
struct ContentView: View {
var body: some View {
RenderView {
Draw([Teapot()]) {
BlinnPhongShader(SimpleMaterial(color: .pink))
}
.camera(PerspectiveCamera())
}
}
} Composable Render PassesCombining shaders in interesting ways is a common task in 3D rendering. Taking inspiration from how easy it is to compose views in SwiftUI, Ultraviolence will let you effortlessly combine render passes. You will be able to experiment with different passes and create reusable components that can be mixed and matched to achieve unique effects. struct MyRenderPass: RenderPass {
@RenderState var downscaled: Texture
@RenderState var upscaled: Texture
var body: some RenderPass {
Chain {
Draw([Teapot()]) {
BlinnPhongShader(SimpleMaterial(color: .pink))
}
.camera(PerspectiveCamera())
.renderTarget(downscaled)
MetalFXUpscaler(input: downscaled)
.renderTarget(upscaled)
Blit(input: upscaled)
}
}
} It’s the Shaders, StupidIn any Metal project, the real action happens in the shaders. But even getting a simple shader up and running requires wading through a lot of setup code. SceneKit and RealityKit often limit what you can do with shaders, and accessing them isn't straightforward. Ultraviolence will make writing and managing your shaders easy, giving you full control over your rendering pipeline. This example shows how to use shaders and shader parameters in Ultraviolence. Note that you should generally not define your shaders as strings in your code. struct MyView: View {
let source = """
#include <metal_stdlib>
using namespace metal;
struct VertexIn {
float4 position [[attribute(0)]];
};
struct VertexOut {
float4 position [[position]];
};
[[vertex]] VertexOut vertex_main(const VertexIn in [[stage_in]], constant float4x4& modelViewProjection [[buffer(0)]]) {
return VertexOut { modelViewProjection * in.position };
}
[[fragment]] float fragment_main() {
return float4(1.0, 0.0, 0.0, 1.0);
}
"""
var modelViewProjection: simd_float4x4
var body: some View {
RenderView {
Draw([Teapot()]) {
VertexShader(name: "vertex_main", source: source)
.parameter("modelViewProjection", modelViewProjection)
FragmentShader(name: "fragment_main", source: source)
}
}
}
}
} TODO: What the above example doesn't show is how to link the vertex descriptor of the geometry to the vertex shader. Mixing Compute and GraphicsThe following example shows how to mix compute and graphics passes in Ultraviolence. struct MyRenderPass: RenderPass {
var geometries: [Geometry]
var color: Texture
var depth: Texture
var body: some RenderPass {
Chain {
Draw(geometries) {
MyGraphicsShader()
}
.colorAttachment(color, index: 0)
.depthAttachment(depth)
.depthCompare(.less)
Compute {
EdgeDetectionKernel()
}
.arguments("color", color)
.arguments("depth", depth)
.output(color)
}
}
} AttachmentsTODO: Different parts of the rendering pipeline must read and write to different textures or with different parameters. Ultraviolence will need to make it easy to manage these attachments flexibly and conveniently. (Some) Batteries IncludedUltraviolence will come with default shaders and utilities to help you get started quickly. While it won’t aim to include every feature, it will provide enough tools to cover common use cases, allowing you to focus on building your unique rendering logic. List of potential "extra" features:
PerformanceUltraviolence should not be significantly slower than writing Metal code by hand. The number of Metal commands generated should be the same as those generated by hand. Render graphs that do not change between frames should be fast to "replay". Misc Notes/Questions/Problems
func render() {
let renderer = Renderer {
Draw([Teapot()]) {{
BlinnPhongShader(SimpleMaterial(color: .pink))
}
.camera(PerspectiveCamera())}
}
let image = try renderer.render(size: [1600, 1200]).cgImage
...
}
|
Beta Was this translation helpful? Give feedback.
-
The README is going to be replaced with a simple placeholder pointing to this discussion for now.
The example code in the README is definitely not what the code is going to look like at this point.
Beta Was this translation helpful? Give feedback.
All reactions