A container for texture data allowing you to create and update textures using your own format.
Use `LowLevelTexture` when you want to bring your own texture data to RealityKit, or update your data frequently.
You update the data on the GPU with Metal compute shaders.
This is ideal for bringing your own textures to RealityKit as-is, or when you intend to update your data frequently.
- Note: For a simpler alternative, consider creating your ``TextureResource`` from a
<doc://com.apple.documentation/documentation/coregraphics/cgimage> with
``TextureResource/init(image:withName:options:)-4qz9s``,
``TextureResource/texture2DArray(slices:named:options:)-50g10``,
``TextureResource/cube(slices:named:options:)-57yj1``, or
``TextureResource/texture3D(slices:named:options:)-6pude``.
Express your texture by creating a ``LowLevelTexture/Descriptor-swift.struct`` that describes
how the data is laid out, along with the size of the texture. This descriptor is similar to
<doc://com.apple.documentation/documentation/metal/mtltexturedescriptor>.
To use `LowLevelTexture`, first configure the descriptor with the desired characteristics of your texture.
```swift
var textureDescriptor: LowLevelTexture.Descriptor {
var desc = LowLevelTexture.Descriptor()
desc.textureType = .type2D
desc.arrayLength = 1
desc.width = 2048
desc.height = 2048
desc.depth = 1
desc.mipmapLevelCount = 1
desc.pixelFormat = .bgra8Unorm
desc.textureUsage = [.shaderRead, .shaderWrite]
desc.swizzle = .init(red: .red, green: .green, blue: .blue, alpha: .alpha)
return desc
}
```
Then, you can initialize the `LowLevelTexture` from its descriptor and provide it to a ``TextureResource``.
```swift
let texture = try LowLevelTexture(descriptor: textureDescriptor)
let resource = try TextureResource(from: texture)
```
You update the contents of a `LowLevelTexture` on the GPU, using a Metal Command Buffer and Compute Command Encoder.
For example, you can write a compute kernel in Metal Shading Language to generate the color of each pixel:
```cpp
kernel void
lowLevelTextureKernel(
texture2d<half, access::write> outTexture [[texture(0)]],
uint2 gid [[thread_position_in_grid]])
{
// Compute texture coordinate ranging from 0 to 1 along each axis.
half2 texCoord {
half(gid[0]) / (outTexture.get_width() - 1),
half(gid[1]) / (outTexture.get_height() - 1)
};
// Compute the color as a linear gradient from top to bottom.
half3 color = mix(
half3 { 0.2, 0.2, 0.8 },
half3 { 0.7, 0.7, 0.9 },
texCoord.y);
// Specify an opacity of 1 if the pixel is within a circle
// spanning the image bounds.
half alpha = length(texCoord - 0.5) < 0.5 ? 1.0h : 0.0h;
// Write the color and opacity to the texture.
outTexture.write(half4(color, alpha), gid);
}
```
You can use this compute kernel to populate the `LowLevelTexture`:
```swift
func populate(texture: LowLevelTexture, device: MTLDevice) {
// Set up the Metal command queue and compute command encoder,
// or abort if that fails.
guard let commandQueue = device.makeCommandQueue(),
let commandBuffer = commandQueue.makeCommandBuffer(),
let computeEncoder = commandBuffer.makeComputeCommandEncoder() else {
return
}
// Load a Metal compute kernel written in Metal Shading Language,
// or abort if that fails.
guard let library = device.makeDefaultLibrary(),
let function = library.makeFunction(name: "lowLevelTextureKernel"),
let computePipelineState = try? device.makeComputePipelineState(function: function) else {
return
}
// Enqueue the Metal command buffer.
commandBuffer.enqueue()
// Set up the Metal compute command encoder with the app's compute kernel.
computeEncoder.setComputePipelineState(computePipelineState)
// Retrieve a MTLTexture from LowLevelTexture.
// This texture will be directly consumed by RealityKit's renderer.
let outTexture: MTLTexture = texture.replace(using: commandBuffer)
computeEncoder.setTexture(outTexture, index: 0)
// Disptach the GPU compute work.
// Note: threadGroupCount and threadGroupSize determined elsewhere.
computeEncoder.dispatchThreadgroups(
threadGroupCount,
threadsPerThreadgroup: threadGroupSize)
// End the encoding and commit the command buffer.
// When the command buffer completes, RealityKit automatically applies the changes.
computeEncoder.endEncoding()
commandBuffer.commit()
}
```
To finish, add your ``TextureResource`` to a ``Material`` and display it on an ``Entity``.
```swift
func textureEntity(device: MTLDevice) throws -> Entity {
// Create the LowLevelTexture and populate it on the GPU.
let texture = try LowLevelTexture(descriptor: textureDescriptor)
populate(texture: texture, device: device)
// Create a TextureResource from the LowLevelTexture.
let resource = try TextureResource(from: texture)
// Create a material that uses the texture.
var material = UnlitMaterial(texture: resource)
material.opacityThreshold = 0.5
// Return an entity of a plane which uses the generated texture.
return ModelEntity(mesh: .generatePlane(width: 1, depth: 1), materials: [material])
}
```

The ``TextureResource`` retains a reference to the `LowLevelTexture`,
and presents changes made to the `LowLevelTexture` when the renderer updates.
Creates a low-level texture from a descriptor.
- Parameter descriptor: An object that defines the structure of the low-level texture.
- Throws: If the descriptor is invalid or if memory was not allocated successfully.
get · iOS 18.0+
descriptor
TypeScript
get descriptor(): Promise<LowLevelTexture_Descriptor>
Swift declaration
@MainActor final let descriptor: LowLevelTexture.Descriptor
The descriptor that you use to initialize the `LowLevelTexture`.
Creates a low-level texture from an existing ``LowLevelDeviceResource`` created and managed by the application.
- Parameter deviceResource: The underlying texture this object should refer to.
- Parameter commandBuffer: The <doc://com.apple.documentation/documentation/metal/mtlcommandbuffer>
you intend to use for texture modifications.
RealityKit waits for the command buffer to complete before utilizing the texture for rendering.
@MainActor func replace(deviceResource: LowLevelDeviceResource, using commandBuffer: (any MTLCommandBuffer)? = nil)
Replaces this object's underlying texture with an existing ``LowLevelDeviceResource`` created and managed by the application.
While it's valid to replace a LowLevelTexture with an externally-managed texture having different dimensions,
the LowLevelTexture will remember its initial size and calling replace(using:) to obtain a new MTLTexture will respect the initial size.
- Parameter deviceResource: The underlying texture this object should refer to.
- Parameter commandBuffer: The <doc://com.apple.documentation/documentation/metal/mtlcommandbuffer>
you intend to use for texture modifications.
RealityKit waits for the command buffer to complete before utilizing the texture for rendering.
@MainActor func replace(using commandBuffer: any MTLCommandBuffer) -> any MTLTexture
Retrieves a Metal texture that your app's shaders can write to when they run on a GPU.
The buffer's contents are in an uninitialized state.
- Parameter commandBuffer: The <doc://com.apple.documentation/documentation/metal/mtlcommandbuffer>
you intend to use for texture modifications.
RealityKit waits for the command buffer to complete before utilizing the texture for rendering.