Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions Sources/CShaderTypes/ShaderTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,8 @@ typedef enum{
ssaoPassFrustumIndex,
ssaoPassReverseZIndex,
ssaoPassProjScaleIndex,
ssaoPassRateMapDataIndex, // rasterization_rate_map_data of the eye's rate map (XR foveation; a never-read placeholder otherwise)
ssaoPassRateMapSizesIndex, // float4: the map's screen size in xy, the depth texture's size in zw; x <= 0 when there is no map
}SSAOBufferIndices;

typedef enum{
Expand Down Expand Up @@ -767,7 +769,9 @@ typedef enum{
hzbCullPassViewportIndex,
hzbCullPassMipCountIndex,
hzbCullPassReverseZIndex,
hzbCullPassOcclusionBiasIndex
hzbCullPassOcclusionBiasIndex,
hzbCullPassRateMapDataIndex, // rasterization_rate_map_data the pyramid was drawn through (XR foveation; a never-read placeholder otherwise)
hzbCullPassRateMapSizesIndex // float4: the map's screen size in xy, the pyramid's base size in zw; x <= 0 when there is no map
}HZBOcclusionCullingBufferIndices;

typedef enum{
Expand Down Expand Up @@ -800,6 +804,8 @@ typedef enum{
gaussianCullHZBReverseZIndex,
gaussianCullHZBOcclusionBiasIndex,
gaussianCullHZBValidIndex,
gaussianCullHZBRateMapDataIndex, // rasterization_rate_map_data the pyramid was drawn through (XR foveation; a never-read placeholder otherwise)
gaussianCullHZBRateMapSizesIndex, // float4: the map's screen size in xy, the pyramid's base size in zw; x <= 0 when there is no map
}GaussianDepthBufferIndices;

typedef enum{
Expand Down Expand Up @@ -1034,7 +1040,8 @@ typedef struct{
uint32_t uniformQuotas; // GaussianDebugOptions.disableScreenWeightedQuotas: screenArea = splatCount, the uniform quota rule
uint32_t paged; // 0: every record resident; 1: a paged entity (the cull writes the demand table and lists resident ranks only, the fused pass reads the page pool); 2: demand only (a warming tier: write the demand word and return)
uint32_t rangeCount; // entries of ranges[] (gaussianChunkCullRangesIndex, GaussianChunkTreeCull); a thread whose chunkIndex falls in none of them skips the per-chunk test. 0 (GaussianDebugOptions.disableTreeSkip, or a file without a tree) skips the check entirely, as before the tree was wired in
}GaussianChunkCullConstants; // 192 bytes (176 + rangeCount, padded to the struct's 16-byte alignment)
simd_float4 hzbRateMapSizes; // the rasterization rate map the HZB was drawn through (XR foveation): its screen size in xy, the pyramid's base size in zw; x <= 0 when there is none and the rate map buffer is a never-read placeholder
}GaussianChunkCullConstants; // 208 bytes (180 + hzbRateMapSizes at 192, padded to the struct's 16-byte alignment)

/// One span of GaussianChunkTreeCull.visibleChunkRanges (Swift): chunk indices [firstChunk,
/// firstChunk + chunkCount) the tree walk could not rule out this frame. Every chunk still gets
Expand Down Expand Up @@ -1062,6 +1069,7 @@ typedef enum{
gaussianChunkCullLevelStateIndex = 11, // GaussianChunkLevelState[chunkCount], persistent, written by gaussianComputeChunkQuotas (a never-read stand-in without coarse levels)
gaussianChunkCullLevelConstantsIndex = 12, // GaussianChunkLevelConstants (setBytes); hasCoarse = 0 keeps every path as it was
gaussianChunkCullRangesIndex = 13, // GaussianChunkCullRange[rangeCount] (setBytes; a never-read stand-in when rangeCount == 0)
gaussianChunkCullRateMapDataIndex = 14, // rasterization_rate_map_data the HZB was drawn through (XR foveation; read only when hzbRateMapSizes.x > 0, a placeholder otherwise)
}GaussianChunkCullBufferIndices;

typedef enum{
Expand Down Expand Up @@ -1299,6 +1307,7 @@ typedef enum{
gaussianChunkPreprocessCoarseTableIndex, // 17: GaussianChunkDecodeConstants[levelCount × chunkCount], the coarse rows, level-major
gaussianChunkPreprocessLevelStateIndex, // 18: GaussianChunkLevelState[chunkCount] as gaussianComputeChunkQuotas left it this frame
gaussianChunkPreprocessLevelConstantsIndex, // 19: GaussianChunkLevelConstants (setBytes)
gaussianChunkPreprocessRateMapDataIndex, // 20: rasterization_rate_map_data the HZB was drawn through (XR foveation; read only when the cull constants' hzbRateMapSizes.x > 0, a placeholder otherwise)
}GaussianChunkPreprocessBufferIndices;

typedef enum{
Expand Down Expand Up @@ -1354,6 +1363,38 @@ typedef enum{
fxaaPassSplatCoverageTextureIndex = 1
}FXAATextureIndices;

// MARK: - Temporal anti-aliasing (TAAShader.metal)

/// The resolve's inputs: the eye's unjittered matrices for this frame and the frame its
/// history holds, and the rasterization rate maps of both when the frames were foveated
/// (XR): the history is laid out in its own frame's physical space, so a pixel goes
/// physical → screen → world → previous screen → previous physical before the history is read.
typedef struct {
matrix_float4x4 invViewProjection; // this frame, unjittered, scene root included
matrix_float4x4 prevViewProjection; // the history's frame, unjittered, scene root included
simd_float4 rateMapSizes; // this frame's map: screen size xy, physical size zw; x <= 0 when drawn uniformly
simd_float4 prevRateMapSizes; // the history frame's map, same layout
simd_float2 physicalSize; // the colour, depth and history textures' size in pixels
float historyWeight; // share of the clipped history in the output (0.9)
uint32_t historyValid; // 0 on the first frame, after a resize or a mode change: the output is the current frame
uint32_t reverseZ;
float clipGamma; // the clip box is the 3×3 mean ± clipGamma × standard deviation (1.25)
uint32_t pad0;
uint32_t pad1;
} TAAConstants; // 176 bytes

typedef enum{
taaPassConstantsIndex = 0, // TAAConstants
taaPassRateMapDataIndex, // rasterization_rate_map_data of this frame (read only when rateMapSizes.x > 0; a placeholder otherwise)
taaPassPrevRateMapDataIndex // rasterization_rate_map_data of the history's frame (read only when prevRateMapSizes.x > 0)
}TAABufferIndices;

typedef enum{
taaPassColorTextureIndex = 0, // this frame's look output
taaPassHistoryTextureIndex, // the previous resolve of this eye
taaPassDepthTextureIndex // this frame's opaque depth
}TAATextureIndices;

typedef enum{
smaaPassTexelSizeIndex,
smaaPassEdgeThresholdIndex,
Expand Down
15 changes: 15 additions & 0 deletions Sources/UntoldEngine/Renderer/Pipelines/RenderPipeLines.swift
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,20 @@ public func InitFXAAPipeline() -> RenderPipeline? {
)
}

/// The temporal resolve: the look output and the eye's history into the anti-aliasing
/// texture and the eye's next history, two colour targets of the look format.
public func InitTAAPipeline() -> RenderPipeline? {
CreatePipeline(
vertexShader: "vertexTAAShader",
fragmentShader: "fragmentTAAShader",
vertexDescriptor: createPostProcessVertexDescriptor(),
colorFormats: [renderInfo.colorPipeline.working.lookOutput, renderInfo.colorPipeline.working.lookOutput],
depthFormat: .invalid,
depthEnabled: false,
name: "TAA Pipeline"
)
}

public func InitFXAAEdgeDebugPipeline() -> RenderPipeline? {
CreatePipeline(
vertexShader: "vertexFXAAShader",
Expand Down Expand Up @@ -1176,6 +1190,7 @@ public func DefaultPipeLines() -> [(RenderPipelineType, RenderPipelineInitBlock)
(.look, InitLookPipeline),
(.fxaa, InitFXAAPipeline),
(.fxaaEdgeDebug, InitFXAAEdgeDebugPipeline),
(.taa, InitTAAPipeline),
(.smaaEdges, InitSMAAEdgesPipeline),
(.smaaBlendWeights, InitSMAABlendWeightsPipeline),
(.smaaNeighborhood, InitSMAANeighborhoodPipeline),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public extension RenderPipelineType {
static let outputTransform: RenderPipelineType = "outputTransform"
static let fxaa: RenderPipelineType = "fxaa"
static let fxaaEdgeDebug: RenderPipelineType = "fxaaEdgeDebug"
static let taa: RenderPipelineType = "taa"
static let smaaEdges: RenderPipelineType = "smaaEdges"
static let smaaBlendWeights: RenderPipelineType = "smaaBlendWeights"
static let smaaNeighborhood: RenderPipelineType = "smaaNeighborhood"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ public extension RenderPassContext {

renderEncoder.label = label
renderEncoder.pushDebugGroup(label)
applyXRFoveationViewport(renderEncoder)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setDepthStencilState(pipeline.depthState)

Expand Down
4 changes: 2 additions & 2 deletions Sources/UntoldEngine/Renderer/RenderInitializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ func requestedOpaqueSampleCount() -> Int {
return 1
#else
switch antiAliasingMode {
case .msaa:
case .msaa, .msaaTaa:
let preferredSampleCount = 4
guard let device = renderInfo.device else { return 1 }
return device.supportsTextureSampleCount(preferredSampleCount) ? preferredSampleCount : 1
case .none, .fxaa, .smaa:
case .none, .fxaa, .smaa, .taa:
return 1
}
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public extension RenderPasses {
}
renderEncoder.label = "Muscle Debug Pass"
renderEncoder.pushDebugGroup("Muscle Debug Pass")
applyXRFoveationViewport(renderEncoder)
renderEncoder.setRenderPipelineState(pipelineState)
// The cages live under the skin, so never depth-test them. In visionOS
// mixed immersion the compositor drops pixels that wrote no depth, so
Expand Down
37 changes: 37 additions & 0 deletions Sources/UntoldEngine/Renderer/RenderPasses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Grid Pass"
applyXRFoveationViewport(renderEncoder)

renderEncoder.pushDebugGroup("Grid Pass")

Expand Down Expand Up @@ -1167,6 +1168,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Sky Pass"
applyXRFoveationViewport(renderEncoder)

renderEncoder.pushDebugGroup("Sky Pass")

Expand Down Expand Up @@ -1237,6 +1239,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Environment Pass"
applyXRFoveationViewport(renderEncoder)

renderEncoder.pushDebugGroup("Environment Pass")

Expand Down Expand Up @@ -1835,6 +1838,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Model Pass"
applyXRFoveationViewport(renderEncoder)

renderEncoder.pushDebugGroup("Model Pass")

Expand Down Expand Up @@ -2102,6 +2106,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Batched Model Pass"
applyXRFoveationViewport(renderEncoder)
renderEncoder.pushDebugGroup("Batched Model Pass")

renderEncoder.setRenderPipelineState(modelPipeline.pipelineState!)
Expand Down Expand Up @@ -2342,6 +2347,10 @@ public enum RenderPasses {

renderEncoder.label = "G-Buffer + Light Pass (TBDR)"
renderEncoder.pushDebugGroup("G-Buffer + Light Pass (TBDR)")
// Through the eye's rasterization rate map when there is one (XR foveation): the
// geometry in the map's screen space, the light quad over the whole of it, the
// G-buffer read back from the tile under each fragment either way.
applyXRFoveationViewport(renderEncoder)

// What every draw of the pass shares, read once.
let pass = OpaquePassState(cameraComponent: cameraComponent)
Expand Down Expand Up @@ -2800,6 +2809,14 @@ public enum RenderPasses {

var projScale = simd_float2(renderInfo.perspectiveSpace.columns.0.x, renderInfo.perspectiveSpace.columns.1.y)
renderEncoder.setFragmentBytes(&projScale, length: MemoryLayout<simd_float2>.stride, index: Int(ssaoPassProjScaleIndex.rawValue))
// The eye's rasterization rate map (XR foveation): the depth was drawn through it, so
// the view positions are rebuilt from where its texels sit on screen, not in the texture.
bindRateMap(
renderInfo.xrFoveation?.rateMapData,
toFragmentsOf: renderEncoder,
dataIndex: Int(ssaoPassRateMapDataIndex.rawValue),
sizesIndex: Int(ssaoPassRateMapSizesIndex.rawValue)
)
// set the draw command

renderEncoder.drawIndexedPrimitivesTracked(
Expand Down Expand Up @@ -3000,6 +3017,14 @@ public enum RenderPasses {

var projScale = simd_float2(renderInfo.perspectiveSpace.columns.0.x, renderInfo.perspectiveSpace.columns.1.y)
renderEncoder.setFragmentBytes(&projScale, length: MemoryLayout<simd_float2>.stride, index: Int(ssaoPassProjScaleIndex.rawValue))
// The eye's rasterization rate map (XR foveation): the depth was drawn through it, so
// the view positions are rebuilt from where its texels sit on screen, not in the texture.
bindRateMap(
renderInfo.xrFoveation?.rateMapData,
toFragmentsOf: renderEncoder,
dataIndex: Int(ssaoPassRateMapDataIndex.rawValue),
sizesIndex: Int(ssaoPassRateMapSizesIndex.rawValue)
)

// SSAO properties
renderEncoder.setFragmentBytes(&SSAOParams.shared.radius, length: MemoryLayout<Float>.stride, index: Int(ssaoPassRadiusIndex.rawValue))
Expand Down Expand Up @@ -3619,6 +3644,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Transparency Pass"
applyXRFoveationViewport(renderEncoder)
renderEncoder.pushDebugGroup("Transparency Pass")

renderEncoder.setRenderPipelineState(transparencyPipeline.pipelineState!)
Expand Down Expand Up @@ -4034,6 +4060,9 @@ public enum RenderPasses {
descriptor.depthAttachment.texture = depthTexture
descriptor.depthAttachment.loadAction = .load
descriptor.depthAttachment.storeAction = .store
// The depth was drawn through the eye's rasterization rate map when there is one
// (XR foveation); so is what this pass adds to it.
attachXRFoveation(renderInfo.xrFoveation?.rateMap, to: descriptor)

guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
handleError(.renderPassCreationFailed, "Mesh Occluder Shell Pass")
Expand All @@ -4047,6 +4076,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Mesh Occluder Shell Pass"
applyXRFoveationViewport(renderEncoder)
renderEncoder.pushDebugGroup("Mesh Occluder Shell Pass")
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setDepthStencilState(pipeline.depthState)
Expand Down Expand Up @@ -4172,6 +4202,9 @@ public enum RenderPasses {
descriptor.depthAttachment.texture = depthTexture
descriptor.depthAttachment.loadAction = .load
descriptor.depthAttachment.storeAction = .store
// The depth was drawn through the eye's rasterization rate map when there is one
// (XR foveation); so is what this pass adds to it.
attachXRFoveation(renderInfo.xrFoveation?.rateMap, to: descriptor)

guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
handleError(.renderPassCreationFailed, "Wireframe Occlusion Depth Pass")
Expand All @@ -4185,6 +4218,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Wireframe Occlusion Depth Pass"
applyXRFoveationViewport(renderEncoder)
renderEncoder.pushDebugGroup("Wireframe Occlusion Depth Pass")
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setDepthStencilState(pipeline.depthState)
Expand Down Expand Up @@ -4369,6 +4403,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Wireframe Pass"
applyXRFoveationViewport(renderEncoder)
renderEncoder.pushDebugGroup("Wireframe Pass")

renderEncoder.setRenderPipelineState(wireframePipelineState)
Expand Down Expand Up @@ -4749,6 +4784,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Spatial Debug Bounds Pass"
applyXRFoveationViewport(renderEncoder)
renderEncoder.pushDebugGroup("Spatial Debug Bounds Pass")

renderEncoder.setRenderPipelineState(spatialDebugPipeline.pipelineState!)
Expand Down Expand Up @@ -4897,6 +4933,7 @@ public enum RenderPasses {
}

renderEncoder.label = "Gaussian Pass"
applyXRFoveationViewport(renderEncoder)
renderInfo.gaussianCoverageWritten = true

renderEncoder.pushDebugGroup("Gaussian Pass")
Expand Down
14 changes: 13 additions & 1 deletion Sources/UntoldEngine/Renderer/RenderResources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ public struct RenderInfo {
/// shadingCameraPosition so each eye gets its own view vector (specular highlights,
/// Fresnel), while streaming, LOD and culling keep the head-centre camera position.
public var xrEyeCameraPosition: simd_float3?
/// The rasterization rate map of the eye being drawn (visionOS foveation), with the
/// viewport and the decoder data that go with it; nil when the frame is drawn uniformly.
/// `viewPort` stays the size of the textures, the map's physical size. See XRFoveation.swift.
public var xrFoveation: XRFoveationFrame?
}

/// Where an eye is, from its view matrix (the inverse of the eye's camera transform).
Expand Down Expand Up @@ -176,6 +180,9 @@ public struct BufferResources {
var reduceScanCapacity: Int = 0
/// Whether a frame with more bounding boxes than the scan takes was reported.
var reportedReduceScanOverflow: Bool = false

/// Bound where a shader declares a rasterization rate map it will not read (XRFoveation.swift).
var rateMapPlaceholder: MTLBuffer?
}

public struct TripleBufferResources {
Expand Down Expand Up @@ -328,9 +335,14 @@ public struct HZBPyramidFrame: Sendable {
public var viewProjection: simd_float4x4
/// Where that frame's camera stood, in world space.
public var cameraPosition: simd_float3
/// The rasterization rate map that frame was drawn through (XR foveation), nil when it was
/// drawn uniformly: the pyramid is laid out in the map's physical space, so a screen rect or
/// point the test projects must go through the map before the pyramid is sampled there.
public var rateMapData: XRRasterizationRateMapData?

public init(viewProjection: simd_float4x4, cameraPosition: simd_float3) {
public init(viewProjection: simd_float4x4, cameraPosition: simd_float3, rateMapData: XRRasterizationRateMapData? = nil) {
self.viewProjection = viewProjection
self.cameraPosition = cameraPosition
self.rateMapData = rateMapData
}
}
Loading
Loading