Skip to main content

Create and own GPU resources

Outcome

GPU resources are durable objects used by one or more submissions. Create them outside the per-frame path when possible, declare every intended usage, reuse them while their contract is stable, and destroy the resources your application owns.

Passes and command encoders are different: they record a unit of work and are not durable scene state.

Resource families

FamilyPurposeTypical lifetime
DataBuffer, Texture, Sampler, ExternalTextureReused while the underlying data or source is valid
Executable stateShader, RenderPipeline, ComputePipelineReused across compatible draws or dispatches
Render targetsFramebuffer, texture views, presentation contextsReused until size, format, or attachment set changes
Synchronization and queriesFence, QuerySetScoped to the measurements or completion tracking they represent
Recorded workCommandEncoder, RenderPass, ComputePass, CommandBufferCreated, finished, and submitted as one bounded unit of work

Engine classes such as Model, Computation, and transform helpers create some of these Core resources internally. The ownership rule remains the same: destroying the Engine owner releases the resources it created; externally supplied resources remain caller-owned unless the exact API states otherwise.

Complete lifecycle

  1. Choose the contract. Decide format, byte size, dimensions, usage, and backend compatibility before allocation.
  2. Create. Use the matching Device factory or pass-begin method.
  3. Initialize. Upload data immediately or encode an ordered copy.
  4. Bind and use. Connect the resource to compatible layouts, pipelines, and passes.
  5. Update or reuse. Change contents without reallocating when the resource contract remains valid.
  6. Replace deliberately. Recreate when size, format, usage, or another structural property changes.
  7. Destroy owned resources. Stop work that may reference them, then release them once.

Creation map

APICreates or begins
device.createBuffer(...)Buffer
device.createTexture(...)Texture
device.createSampler(...)Sampler
device.createFramebuffer(...)Framebuffer
device.createShader(...)Shader
device.createRenderPipeline(...)RenderPipeline
device.createComputePipeline(...)ComputePipeline
device.createCommandEncoder(...)CommandEncoder
commandEncoder.beginRenderPass(...)RenderPass
commandEncoder.beginComputePass(...)ComputePass
device.createRenderBundleEncoder(...)RenderBundleEncoder

Some convenience methods begin a pass through the device’s current encoder. Prefer an explicit encoder when copies, compute, and rendering need one visible ordering and submission boundary.

Ownership questions to answer

For each resource, make these decisions explicit:

  • Who calls destroy()?
  • May another object retain or borrow it?
  • Can in-flight GPU work still reference it?
  • Does resizing or reconfiguration replace it?
  • Does a cache or factory share it across models?

Resource sharing is useful only when ownership remains unambiguous. Passing a buffer into another object does not automatically transfer ownership.

Common mistakes

  • Allocating a new resource for a contents-only update.
  • Omitting a usage needed by a later copy, binding, or render attachment.
  • Treating passes and encoders as reusable resources after they are ended or finished.
  • Destroying caller-supplied resources from a wrapper that only borrowed them.
  • Retaining size-dependent framebuffers or textures after the canvas size changed.

Next steps