A resource and a view are different objects#
A resource owns GPU-accessible storage. A descriptor tells the pipeline how to interpret or use that storage. The same compatible resource may have different views of different subresources, but a descriptor is not a copy of the resource’s bytes. DX12 uses RTV and DSV heaps for attachment views, and CBV/SRV/UAV and sampler heaps for shader bindings. Shader-visible heaps have GPU handles. CPU descriptor handles are addresses for descriptor creation and copying; they are not interchangeable with GPU handles or GPU virtual addresses.
Follow the sample’s bindings#
Sample::Init creates rtvs, srvs, and dsvs. CreateTargets fills attachment descriptors. CreateTexture fills the single texture SRV after its initialization copy completes. CreatePipeline defines the root signature’s register contract. Render sets the SRV heap and binds the table’s GPU start handle. The b0 matrix is root constants, so it does not need a CBV descriptor. A larger renderer can move frame data into a constant buffer with a 256-byte-aligned allocation and appropriate CBV size. That change requires an explicitly updated root signature, binding call, and HLSL contract.
D3D12_SHADER_RESOURCE_VIEW_DESC srv{};
srv.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srv.Texture2D.MipLevels = 1;
device->CreateShaderResourceView(texture.Get(), &srv,
srvs->GetCPUDescriptorHandleForHeapStart());This excerpt uses Sample’s already-created device, texture, and srvs. The texture format matches the sRGB color sampling intent. Data textures require a linear format/view strategy.
Memory intent guides allocation#
Upload heaps permit CPU writes and remain in GENERIC_READ. They are useful for staging and small dynamic data. Default heaps hold frequently reused GPU resources; initialization copies populate them. Readback heaps support CPU inspection of GPU results after completion, with the relevant copy destination state. The teaching sample leaves static vertex/index data in upload heaps. The renderer chapter moves static geometry into default resources to avoid repeatedly accessing CPU-oriented memory on discrete GPUs. Committed resources are straightforward; placed resources and pools need additional allocation and aliasing rules.
Frame-owned updates#
A persistent mapping is not a synchronization guarantee. If a CPU overwrites bytes while an earlier frame reads them, the image can flicker even when every resource state is valid. Reserve an aligned region for each in-flight frame, or use a ring whose spans are retired by completion values. Descriptor reuse has the same hazard. Updating a slot referenced by a submitted draw can change what that draw observes. QubicEngine reserves frame-owned descriptor regions for transient bindings and fence-retires persistent slot changes. A stable material handle resolves a descriptor version at a defined boundary.
Reference implementation contract
UploadArena owns a mapped ID3D12Resource, a capacity, a cursor, and a completion value. Allocate rounds the cursor to the requested alignment, checks remaining capacity, and returns a CPU pointer plus a GPU virtual address. Reset rewinds only after the arena’s fence passes. Overflow selects another retained page or reports a budget error; it does not overwrite active bytes. DescriptorArena similarly owns a region of one shader-visible heap. A slot allocation returns its index plus the heap’s CPU/GPU handles computed with the descriptor increment. A command list must bind the correct heap before a table handle from it is used.
Diagnose binding problems#
A descriptor table with the wrong range or register can cause validation errors or incorrect sampling. A CPU handle passed where a GPU handle is required is a contract error. A view with an incompatible format is a resource/view error. Intermittent corruption after a few frames often points to reuse rather than shader math. Check heap type, shader visibility, root parameter index, register/space, table offset, descriptor format, subresource selection, and last-use fence in that order. Continue with the complete transfer and native synchronization.