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.