Skip to main content

GPU Shaders

Low-level GPU APIs for shader assets, custom render passes, texture sampling, MSAA, post-processing, and 3D-style geometry.

Preview / rollout-dependent

This API family is early-access material. The runtime surface exists, but editor shader-asset workflow and packaging availability can vary by channel. Always guard optional shader handles and keep examples easy to disable when a workspace is not shader-enabled.

Current naming policy

Use context:shader(name) for shader lookup and GPUCanvas.format for render-target format. Do not use older context:loadShader(name) or context:preferredCanvasFormat() names in new LERP examples.


Mental Model

A shader workflow has three layers:

LayerWhat it contains
Shader assetA .wgsl shader file added to the Rive file and packaged as a compiled shader asset
Luau scriptResource creation, buffers, bind groups, pipelines, render passes, and image compositing
Runtime GPU backendBackend-specific shader variants, texture views, render targets, and command submission

Script authors normally work with the first two layers:

local shader = context:shader("my_effect")
if not shader then
return false
end

local canvas = context:gpuCanvas({ width = 512, height = 512 })
local colorTargets: { ColorTarget } = {
({ format = canvas.format } :: ColorTarget),
}
local pipeline = GPUPipeline.new({
vertex = { module = shader, entryPoint = "vsMain" },
fragment = { module = shader, entryPoint = "fsMain" },
vertexLayout = layouts,
colorTargets = colorTargets,
})

Implementation details such as RSTB shader payloads, backend shader variants, and reflected binding metadata matter to parser/tooling authors, but normal Rive scripts do not construct those payloads directly.


Type-Safe Luau Construction

Rive GPU descriptors are checked Luau table types. Prefer typed locals over inline nested descriptor arrays:

local attributes: { VertexAttribute } = {
({ slot = 0, format = "float32x2", offset = 0 } :: VertexAttribute),
({ slot = 1, format = "float32x2", offset = 2 * 4 } :: VertexAttribute),
}

local vertexLayout: { VertexBufferLayout } = {
({ stride = 4 * 4, attributes = attributes } :: VertexBufferLayout),
}

local colorTargets: { ColorTarget } = {
({ format = canvas.format } :: ColorTarget),
}

Use the same pattern for bind groups:

local ubos: { UBOEntry } = {
({ slot = 0, buffer = uniformBuffer, offset = 0, size = UNIFORM_SIZE } :: UBOEntry),
}
local textures: { TextureEntry } = {
({ slot = 1, view = image:view() } :: TextureEntry),
}
local samplers: { SamplerEntry } = {
({ slot = 2, sampler = gpuSampler } :: SamplerEntry),
}

Create GPU resources in non-null locals inside init, call methods on those locals, then assign them to optional self.* fields after setup succeeds. In drawCanvas, localize optional fields and return before issuing render work if any required handle is missing.


Context Methods

context:gpuCanvas(desc?)

Creates a GPU render target that can be drawn into with GPURenderPass and composited through GPUCanvas.image.

context:gpuCanvas(desc: {
width: number,
height: number,
}?): GPUCanvas

Use a descriptor when the size is known:

self.gpuCanvas = context:gpuCanvas({ width = 512, height = 512 })

Omit the descriptor for a deferred canvas:

self.gpuCanvas = context:gpuCanvas()

Deferred canvases have no backing texture until resized. Check canvas.width > 0 before rendering or calling colorView().

context:features()

Returns GPU feature flags and limits for the current backend.

context:features(): GPUFeatures

Use it before relying on optional behavior:

local features = context:features()
local sampleCount = 1

if features.maxSamples >= 4 then
sampleCount = 4
elseif features.maxSamples >= 2 then
sampleCount = 2
end

context:shader(name)

Gets a compiled shader by asset name, without the .wgsl extension.

context:shader(name: string): Shader?

Example:

local shader = context:shader("gradient_card")
if not shader then
print("Missing shader asset: gradient_card.wgsl")
return false
end

The return type is optional because the shader asset may be missing, named differently, not compiled, not packaged, or unavailable in the current editor/runtime channel. Pass the asset name without the .wgsl extension.

context:canvas(desc?)

Creates a normal 2D Rive renderer canvas, not a shader canvas.

context:canvas(desc: {
width: number,
height: number,
clearColor: Color?,
}?): Canvas

Use Canvas when you want to draw normal Rive renderer content into an image. Use GPUCanvas when you need GPU render passes and shader output.


Shader

Shader is an opaque compiled shader module.

local shader: Shader? = context:shader("my_effect")

Use a shader directly:

vertex = shader
fragment = shader

Or select named entry points:

vertex = { module = shader, entryPoint = "vsMain" }
fragment = { module = shader, entryPoint = "fsMain" }

Vertex and fragment stages may come from the same shader asset or different shader assets. Named entry points are recommended for non-trivial shaders.


Canvas vs GPUCanvas

Canvas

Canvas renders normal Rive renderer commands into an image.

declare extern type Canvas with
image: Image
width: number
height: number
function resize(self, width: number, height: number): ()
function beginFrame(self, desc: { clearColor: Color? }?): Renderer
function endFrame(self): ()
end

Use beginFrame / endFrame inside drawCanvas.

GPUCanvas

GPUCanvas renders GPU passes and exposes the result as an Image.

declare extern type GPUCanvas with
image: Image
width: number
height: number
format: ColorFormat
function resize(self, width: number, height: number): ()
function colorView(self): GPUTextureView
function beginRenderPass(self, desc: RenderPassDesc): GPURenderPass
end
Field or methodMeaning
imageBacking image for renderer:drawImage(...) compositing
width, heightPixel dimensions; deferred canvases report 0
formatPixel format of the backing texture; use for pipeline color targets
resize(w, h)Recreates the backing texture
colorView()Returns a GPUTextureView for the backing texture
beginRenderPass(desc)Opens a render pass; call inside drawCanvas

GPURenderPass

An active GPU render pass. Bind resources, draw, then call finish().

declare extern type GPURenderPass with
function setPipeline(self, pipeline: GPUPipeline): ()
function setVertexBuffer(self, slot: number, buffer: GPUBuffer): ()
function setIndexBuffer(self, buffer: GPUBuffer, format: ("uint16" | "uint32")?): ()
function setBindGroup(self, groupIndex: number, bg: GPUBindGroup, dynamicOffsets: {number}?): ()
function setViewport(self, x: number, y: number, w: number, h: number): ()
function setScissorRect(self, x: number, y: number, w: number, h: number): ()
function setStencilReference(self, ref: number): ()
function setBlendColor(self, r: number, g: number, b: number, a: number): ()
function draw(self, vertexCount: number, instanceCount: number?, firstVertex: number?): ()
function drawIndexed(self, indexCount: number, instanceCount: number?, firstIndex: number?): ()
function finish(self): ()
end

Runtime v0.1.106 also accepts optional firstInstance for draw and optional baseVertex / firstInstance for drawIndexed. Use non-zero base-instance forms only when context:features().drawBaseInstance is true.

Basic pattern:

local pass = self.gpuCanvas:beginRenderPass({
color = {{
loadOp = "clear",
storeOp = "store",
clearColor = { 0, 0, 0, 0 },
}},
})

pass:setPipeline(self.pipeline)
pass:setVertexBuffer(0, self.vertexBuffer)
pass:setBindGroup(0, self.bindGroup)
pass:draw(self.vertexCount)
pass:finish()

GPUPipeline

Compiled shader stages plus fixed-function render state.

declare extern type GPUPipeline with
function getBindGroupLayout(self, groupIndex: number): GPUBindGroupLayout
end

declare GPUPipeline: {
new: @checked (desc: {
vertex: PipelineStage,
fragment: PipelineStage?,
vertexLayout: { VertexBufferLayout },
bindGroupLayouts: { GPUBindGroupLayout }?,
colorTargets: { ColorTarget }?,
depthStencil: DepthStencilState?,
cullMode: CullMode?,
topology: PrimitiveTopology?,
sampleCount: number?,
}) -> GPUPipeline,
}

Constructor Fields

FieldRequiredMeaning
vertexyesVertex shader stage
fragmentnoFragment shader stage; optional for depth-only passes
vertexLayoutyesVertex buffer layout array
bindGroupLayoutsnoExplicit layouts for shared resources
colorTargetsnoColor output formats and blend states
depthStencilnoDepth/stencil state
cullModeno"none", "front", or "back"
topologynoPrimitive assembly mode
sampleCountnoMSAA sample count; default 1

Runtime-observed advanced fields include winding, stencilFront, stencilBack, stencilReadMask, stencilWriteMask, ColorTarget.writeMask, and DepthStencilState.depthBiasClamp. Feature-gate depth-bias clamp with context:features().depthBiasClamp.

getBindGroupLayout(groupIndex) is intended for pipelines with auto-derived layouts after the GPUPipeline exists. If you need shared layouts across multiple pipelines, dynamic uniform-buffer bindings, or an auditable binding contract before pipeline construction, create an explicit GPUBindGroupLayout.new(...) and pass it in bindGroupLayouts.

PipelineStage

export type PipelineStage = Shader | { module: Shader, entryPoint: string? }

GPUBuffer

GPU memory for vertices, indices, or uniforms.

declare extern type GPUBuffer with
size: number
function write(self, data: buffer, offset: number?): ()
end

declare GPUBuffer: {
new: @checked (desc: GPUBufferDesc) -> GPUBuffer,
}

GPUBufferDesc

export type GPUBufferDesc = {
size: number,
usage: BufferUsageArg,
data: buffer?,
immutable: boolean?,
label: string?,
}
FieldMeaning
sizeSize in bytes
usage"vertex", "index", or "uniform"
dataOptional initial bytes
immutableGPU-only after creation when true; initial data required
labelOptional debug name
local vbo = GPUBuffer.new({
size = buffer.len(vertexBytes),
usage = "vertex",
data = vertexBytes,
immutable = true,
label = "static mesh vertices",
})

Dynamic uniform update:

buffer.writef32(self.uniformBytes, 0, self.time)
buffer.writef32(self.uniformBytes, 4, self.strength)
self.uniformBuffer:write(self.uniformBytes, 0)

GPUTexture and GPUTextureView

GPUTexture

GPU texture data for color, depth, arrays, cubes, 3D textures, render targets, and uploaded pixels.

declare GPUTexture: {
new: @checked (desc: {
width: number,
height: number,
format: TextureFormat?,
type: TextureType?,
renderTarget: boolean?,
mipmaps: number?,
layers: number?,
sampleCount: number?,
label: string?,
}) -> GPUTexture,
}
FieldDefaultMeaning
width, heightrequiredTexture size in texels
format"rgba8unorm"Pixel format
type"2d""2d", "cube", "3d", or "2d-array"
renderTargetfalseAllows use as color/depth render attachment
mipmaps1Mip level count
layers1Array layers or cube faces
sampleCount1MSAA sample count
labelnoneDebug label

MSAA rules:

  • MSAA textures use sampleCount > 1 and renderTarget = true.
  • MSAA textures cannot have mipmaps.
  • MSAA textures cannot be uploaded to or sampled directly.
  • Resolve MSAA into a 1x texture or GPUCanvas:colorView().

Methods

texture:view(desc?: {
dimension: TextureType?,
aspect: TextureAspect?,
baseMipLevel: number?,
mipCount: number?,
baseLayer: number?,
layerCount: number?,
}): GPUTextureView
texture:upload(desc: {
data: buffer,
mipLevel: number?,
layer: number?,
width: number?,
height: number?,
}): ()

GPUTextureView

declare extern type GPUTextureView with
format: TextureFormat
end

Use a texture view as a render attachment, MSAA resolve target, depth/stencil attachment, shader texture binding, or Image:view() result.


GPUSampler

Controls how a shader samples a texture.

declare GPUSampler: {
new: @checked (desc: {
min: Filter?,
mag: Filter?,
mipmap: Filter?,
wrapU: WrapMode?,
wrapV: WrapMode?,
compare: CompareFunction?,
maxAnisotropy: number?,
}?) -> GPUSampler,
}
FieldDefaultMeaning
min, mag, mipmap"linear"Minification, magnification, and mip filtering
wrapU, wrapV"clamp-to-edge"Horizontal and vertical wrap modes
compareomittedDepth comparison function
maxAnisotropy1Anisotropic filtering level

Runtime-observed additions include wrapW, minLod, and maxLod. Use anisotropy above 1 only when context:features().anisotropicFiltering is true.

local smoothSampler = GPUSampler.new({
min = "linear",
mag = "linear",
mipmap = "linear",
wrapU = "clamp-to-edge",
wrapV = "clamp-to-edge",
})

Bind Groups

Bind groups connect WGSL @group / @binding declarations to Luau resources.

WGSL:

@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var imageTex: texture_2d<f32>;
@group(0) @binding(2) var imageSampler: sampler;

Luau:

local layout = pipeline:getBindGroupLayout(0)
local ubos: { UBOEntry } = {
({ slot = 0, buffer = uniformBuffer, offset = 0, size = UNIFORM_SIZE } :: UBOEntry),
}
local textures: { TextureEntry } = {
({ slot = 1, view = sourceImage:view() } :: TextureEntry),
}
local samplers: { SamplerEntry } = {
({ slot = 2, sampler = gpuSampler } :: SamplerEntry),
}

local bindGroup = GPUBindGroup.new({
layout = layout,
ubos = ubos,
textures = textures,
samplers = samplers,
})

GPUBindGroupLayout

declare GPUBindGroupLayout: {
new: @checked (desc: {
shader: Shader,
groupIndex: number,
dynamicUBOs: { number }?,
label: string?,
}) -> GPUBindGroupLayout,
}

Use explicit layouts when multiple pipelines share the same resources.

GPUBindGroup

declare GPUBindGroup: {
new: @checked (desc: {
layout: GPUBindGroupLayout,
ubos: { UBOEntry }?,
textures: { TextureEntry }?,
samplers: { SamplerEntry }?,
}) -> GPUBindGroup,
}

Entry types:

export type UBOEntry = {
slot: number,
buffer: GPUBuffer,
offset: number?,
size: number?,
}

export type TextureEntry = {
slot: number,
view: GPUTextureView,
}

export type SamplerEntry = {
slot: number,
sampler: GPUSampler,
}

UBOEntry.size is the byte size of the bound range. It is not a number of fields. For example:

WGSL uniform payloadTypical size
time, width, height, radius as four f32 values16
one mat4x4<f32>64
dynamic per-object uniform recordpadded stride such as 256

Keep the byte size aligned with the WGSL uniform struct layout. If the same buffer holds multiple records, offset selects the byte start and size selects the byte range exposed to that binding.

Dynamic UBO Offsets

Dynamic offsets are matched to dynamic bindings in the bind-group layout, not to arbitrary ubos insertion order. Keep dynamic bindings in ascending WGSL binding order.

local layout = GPUBindGroupLayout.new({
shader = shader,
groupIndex = 0,
dynamicUBOs = { 0, 3 },
})

pass:setBindGroup(0, bindGroup, { cameraOffset, materialOffset })

Rules:

  • One offset per dynamic UBO binding.
  • Offsets are byte offsets into the GPUBuffer.
  • Use 256-byte alignment for dynamic UBO records.
  • The offset count must match the dynamic UBO binding count exactly.

Descriptor Types

VertexAttribute

export type VertexAttribute = {
format: VertexFormat,
slot: number,
offset: number?,
}

The slot maps to WGSL @location(N).

VertexBufferLayout

export type VertexBufferLayout = {
stride: number,
stepMode: ("vertex" | "instance")?,
attributes: { VertexAttribute },
}

BlendState

export type BlendState = {
srcColor: BlendFactor?,
dstColor: BlendFactor?,
colorOp: BlendOp?,
srcAlpha: BlendFactor?,
dstAlpha: BlendFactor?,
alphaOp: BlendOp?,
}

Common alpha blending:

blend = {
srcColor = "src-alpha",
dstColor = "one-minus-src-alpha",
colorOp = "add",
srcAlpha = "one",
dstAlpha = "one-minus-src-alpha",
alphaOp = "add",
}

ColorTarget

export type ColorTarget = {
format: ColorFormat?,
blend: BlendState?,
writeMask: string?, -- runtime-observed advanced field
}

Use format = canvas.format when rendering to a GPUCanvas.

DepthStencilState

export type DepthStencilState = {
format: DepthFormat?,
compare: CompareFunction?,
write: boolean?,
depthBias: number?,
depthBiasSlopeScale: number?,
depthBiasClamp: number?, -- feature-gated
}

Standard depth:

depthStencil = {
format = "depth24plus-stencil8",
compare = "less",
write = true,
}

Reverse-Z:

depthStencil = {
format = "depth32float",
compare = "greater",
write = true,
}

Clear reverse-Z depth attachments to 0.0.

RenderPassDesc

export type RenderPassDesc = {
color: { ColorAttachment }?,
depthStencil: DepthStencilAttachment?,
}

ColorAttachment

export type ColorAttachment = {
view: GPUTextureView?,
resolveTarget: GPUTextureView?,
loadOp: LoadOp?,
storeOp: StoreOp,
clearColor: GPUColor?,
}

Omit view when rendering directly to the receiving GPUCanvas backing texture. Use explicit view and resolveTarget for MSAA.

DepthStencilAttachment

export type DepthStencilAttachment = {
view: GPUTextureView,
depthLoadOp: LoadOp?,
depthStoreOp: StoreOp,
depthClearValue: number?,
}

String Literal Types

Formats

export type ColorFormat =
"r8unorm" | "rg8unorm" | "rgba8unorm" | "bgra8unorm"
| "rgba16float" | "rg16float" | "r16float"
| "rgba32float" | "rgb10a2unorm" | "rg11b10ufloat"

export type DepthFormat =
"depth16unorm" | "depth24plus-stencil8"
| "depth32float" | "depth32float-stencil8"

export type CompressedFormat =
"bc1-rgba-unorm" | "bc3-rgba-unorm" | "bc7-rgba-unorm"
| "etc2-rgb8unorm" | "etc2-rgba8unorm"
| "astc-4x4-unorm" | "astc-6x6-unorm" | "astc-8x8-unorm"

export type TextureFormat = ColorFormat | DepthFormat | CompressedFormat

Check GPUFeatures before relying on compressed texture families.

Texture Types and Aspects

export type TextureType = "2d" | "cube" | "3d" | "2d-array"
export type TextureAspect = "all" | "depth-only" | "stencil-only"

Compare Functions

export type CompareFunction =
"never" | "less" | "equal" | "less-equal"
| "greater" | "not-equal" | "greater-equal" | "always"

Primitive Topology

export type PrimitiveTopology =
"triangle-list" | "triangle-strip" | "line-list" | "line-strip" | "point-list"

Load and Store

export type LoadOp = "clear" | "load"
export type StoreOp = "store" | "discard"

Prefer clear over load unless previous contents are required. Use discard for transient MSAA/depth attachments after resolve.

Vertex Formats

export type VertexFormat =
"float32" | "float32x2" | "float32x3" | "float32x4"
| "uint8x4" | "unorm8x4" | "snorm8x4"
| "float16x2" | "float16x4"

Sampler Modes

export type Filter = "nearest" | "linear"
export type WrapMode = "repeat" | "mirror-repeat" | "clamp-to-edge"

Blending

export type BlendFactor =
"zero" | "one" | "src" | "one-minus-src"
| "src-alpha" | "one-minus-src-alpha"
| "dst" | "one-minus-dst"
| "dst-alpha" | "one-minus-dst-alpha"
| "src-alpha-saturated" | "constant" | "one-minus-constant"

export type BlendOp = "add" | "subtract" | "reverse-subtract" | "min" | "max"

GPUFeatures

export type GPUFeatures = {
bc: boolean,
etc2: boolean,
astc: boolean,
maxTextureSize2D: number,
maxTextureSizeCube: number,
maxTextureSize3D: number,
anisotropicFiltering: boolean,
texture3D: boolean,
textureArrays: boolean,
colorBufferFloat: boolean,
colorBufferHalfFloat: boolean,
perTargetBlend: boolean,
perTargetWriteMask: boolean,
drawBaseInstance: boolean,
depthBiasClamp: boolean,
maxColorAttachments: number,
maxUniformBufferSize: number,
maxSamplers: number,
maxSamples: number,
}

Important feature gates:

FeatureGate
BC compressed texturesfeatures.bc
ETC2 compressed texturesfeatures.etc2
ASTC compressed texturesfeatures.astc
3D texturesfeatures.texture3D
Texture arraysfeatures.textureArrays
Anisotropic filteringfeatures.anisotropicFiltering
Float color render targetsfeatures.colorBufferFloat
Half-float color render targetsfeatures.colorBufferHalfFloat
Independent color-target blendingfeatures.perTargetBlend
Independent color-target write masksfeatures.perTargetWriteMask
Non-zero first-instance draw formsfeatures.drawBaseInstance
Depth bias clampfeatures.depthBiasClamp
MSAA sample countfeatures.maxSamples

Image, Color, and Buffer Utilities

Image:view()

Returns a GPUTextureView for sampling an image asset in a shader.

local image = context:image("source")
if image then
local view = image:view()
end

Use it in a bind group:

local textures: { TextureEntry } = {
({ slot = 1, view = image:view() } :: TextureEntry),
}

Image-dependent shader examples need an actual Rive image asset in the file. The example pack uses demo_image; change that script constant when the artboard uses a different asset name.

Color.toFloat(color)

Converts a Rive Color to { r, g, b, a } floats in the 0..1 range.

local clear = Color.toFloat(Color.rgba(255, 0, 0, 128))

Buffer Helpers

GPU-related buffer helpers include:

buffer.readf16(b, offset) -> number
buffer.writef16(b, offset, value) -> ()
buffer.stridedcopy(
dst, dstOffset, dstStride,
src, srcOffset, srcStride,
elementSize, count
)
buffer.convert(
dst, dstOffset, dstFormat,
src, srcOffset, srcFormat,
count, components?, dstStride?, srcStride?
)

Common conversion formats include f16, f32, u8, u8norm, i8norm, u16, u16norm, i16norm, and u32.


Common Rendering Patterns

Render to GPUCanvas

local pass = canvas:beginRenderPass({
color = {{
loadOp = "clear",
storeOp = "store",
clearColor = { 0, 0, 0, 0 },
}},
})

pass:setPipeline(pipeline)
pass:setVertexBuffer(0, vertexBuffer)
pass:draw(vertexCount)
pass:finish()

MSAA Resolve

color = {{
view = msaaColor:view(),
resolveTarget = canvas:colorView(),
loadOp = "clear",
storeOp = "discard",
clearColor = { 0, 0, 0, 0 },
}}

The MSAA color texture format and pipeline sampleCount must match.

Offscreen Post-process

  1. Render scene into a render-target GPUTexture.
  2. Create a bind group that samples that texture view.
  3. Render a fullscreen quad into the final GPUCanvas.

Use this for blur, bloom, color grading, masks, and any distortion that needs a source image. A shader cannot automatically sample the already-composited Rive framebuffer behind the current node. For glass or background distortion, render the content you want to distort into a texture or canvas first, then sample that explicit source.

Per-object Dynamic Uniforms

local stride = 256
pass:setBindGroup(0, objectBindGroup, { objectIndex * stride })
pass:drawIndexed(indexCount)

What This API Is Not

The GPU API does not provide a full scene graph, material system, lighting system, skeletal 3D runtime, or direct USD/OBJ/glTF/GLB/FBX importer. You can render 3D-like content when your script supplies buffers, textures, uniforms, matrices, and shader assets.


Troubleshooting

ProblemLikely fix
context:shader(name) returns nilCheck asset name without .wgsl, shader compile status, shader packaging, and editor/runtime channel
colorView() throwsResize deferred GPUCanvas before rendering
Nothing rendersReturn drawCanvas from the factory, call pass:finish(), and composite gpuCanvas.image in draw
Interleaved attributes read wrongVerify every byte offset and stride
Bind group mismatchMatch @group and @binding to bind group index and slot
Luau rejects vertexLayout, colorTargets, ubos, textures, or samplersMove nested descriptors into explicitly typed locals such as { VertexAttribute }, { VertexBufferLayout }, { ColorTarget }, { UBOEntry }, { TextureEntry }, and { SamplerEntry }
getBindGroupLayout is unavailable or mismatchedConfirm the pipeline was built with an auto layout for that group, or use explicit GPUBindGroupLayout.new(...)
Dynamic UBO offset mismatchUse layout binding order and 256-byte alignment
MSAA resolve failsMatch formats/sample counts; do not sample MSAA textures directly
Image sampling example cannot startAdd the required Rive image asset or update the script's asset-name constant
Texture output looks blurryMatch GPUCanvas dimensions to display size, choose the intended GPUSampler and ImageSampler, and avoid accidental UV minification
WGSL rejects a swizzle write such as color.rgb = ...Construct a new value, for example vec4<f32>(newRgb, color.a)
Glass distortion samples the wrong thingRender a source texture first; the shader cannot read the already-composited framebuffer automatically
Depth is invertedMatch projection style, depth clear value, and compare function

See Also