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
| Family | Purpose | Typical lifetime |
|---|---|---|
| Data | Buffer, Texture, Sampler, ExternalTexture | Reused while the underlying data or source is valid |
| Executable state | Shader, RenderPipeline, ComputePipeline | Reused across compatible draws or dispatches |
| Render targets | Framebuffer, texture views, presentation contexts | Reused until size, format, or attachment set changes |
| Synchronization and queries | Fence, QuerySet | Scoped to the measurements or completion tracking they represent |
| Recorded work | CommandEncoder, RenderPass, ComputePass, CommandBuffer | Created, 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
- Choose the contract. Decide format, byte size, dimensions, usage, and backend compatibility before allocation.
- Create. Use the matching
Devicefactory or pass-begin method. - Initialize. Upload data immediately or encode an ordered copy.
- Bind and use. Connect the resource to compatible layouts, pipelines, and passes.
- Update or reuse. Change contents without reallocating when the resource contract remains valid.
- Replace deliberately. Recreate when size, format, usage, or another structural property changes.
- Destroy owned resources. Stop work that may reference them, then release them once.
Creation map
| API | Creates 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
- GPU memory explains transfer and readback cost.
- Bindings connects resources to shader interfaces.
- Issuing GPU commands explains recording and submission.
Resourcedefines the shared resource contract.