Advertisement

"An ID3D12Resource object is referenced by GPU operations in-flight on Command Queue": When and How Should I Create my Buffers?

Started by January 11, 2023 12:00 AM
3 comments, last by Gnollrunner 1 year, 10 months ago

When trying to re-create my vertex buffers, my app crashes and I get this error:

D3D12 ERROR: ID3D12Resource2::: CORRUPTION: An ID3D12Resource object (0x000001DDEBA98FC0:'Vertex Buffer Default Resource Heap') is referenced by GPU operations in-flight on Command Queue (0x000001DDEB80E3A0:'Unnamed ID3D12CommandQueue Object'). It is not safe to final-release objects that may have GPU operations pending. This can result in application instability. [ EXECUTION ERROR #921: OBJECT_DELETED_WHILE_STILL_IN_USE]

Here is my function called create_vertex_buffers():

// a triangle
Vertex verts[] = {
    Vertex(0.0f, 0.5f, 0.5f),
    Vertex(0.5f, -0.5f, 0.5f),
    Vertex(-0.5f, -0.5f, 0.5f),
};

const UINT verts_size = sizeof(verts);

// create default heap
auto buffer = CD3DX12_RESOURCE_DESC::Buffer(verts_size);
auto heap_properties1 = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
device->CreateCommittedResource(
    &heap_properties1, // a default heap
    D3D12_HEAP_FLAG_NONE, // no flags
    &buffer, // resource description for a buffer
    D3D12_RESOURCE_STATE_COPY_DEST,
    nullptr,
    IID_PPV_ARGS(&vertex_buffer));

// create upload heap
// upload heaps are used to upload data to the GPU. CPU can write to it, GPU can read from it
// We will upload the vertex buffer using this heap to the default heap
auto heap_properties2 = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
auto buffer2 = CD3DX12_RESOURCE_DESC::Buffer(verts_size);
device->CreateCommittedResource(
    &heap_properties2, // upload heap
    D3D12_HEAP_FLAG_NONE, // no flags
    &buffer2, // resource description for a buffer
    D3D12_RESOURCE_STATE_GENERIC_READ,
    nullptr,
    IID_PPV_ARGS(&vertex_buffer_upload));

D3D12_SUBRESOURCE_DATA vertex_data = {};
vertex_data.pData = reinterpret_cast<BYTE*>(verts); // pointer to our vertex array
vertex_data.RowPitch = verts_size; // size of all our triangle vertex data
vertex_data.SlicePitch = verts_size; // also the size of our triangle vertex data

UpdateSubresources(command_list.Get(), vertex_buffer.Get(), vertex_buffer_upload.Get(), 0, 0, 1, &vertex_data);

auto transition = CD3DX12_RESOURCE_BARRIER::Transition(vertex_buffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
command_list->ResourceBarrier(1, &transition);

vertex_buffer_view.BufferLocation = vertex_buffer->GetGPUVirtualAddress();
vertex_buffer_view.StrideInBytes = sizeof(Vertex);
vertex_buffer_view.SizeInBytes = verts_size;

It is my function for creating the vertex upload and default buffers. I implement it into my command queue like this:

CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(render_targets[frame_index].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
command_list->ResourceBarrier(1, &barrier);

// -------------------------+
create_vertex_buffers(); // | <== This is where I'm calling it!!
// -------------------------+

CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_handle(rtv_descriptor_heap->GetCPUDescriptorHandleForHeapStart(), frame_index, rtv_descriptor_size);
CD3DX12_CPU_DESCRIPTOR_HANDLE dsv_handle(depth_stencil_descriptor_heap->GetCPUDescriptorHandleForHeapStart());

command_list->OMSetRenderTargets(1, &rtv_handle, FALSE, &dsv_handle);

command_list->ClearRenderTargetView(rtv_handle, color, 0, nullptr);
command_list->ClearDepthStencilView(depth_stencil_descriptor_heap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

command_list->SetPipelineState(pipeline_state_object.Get());
command_list->SetGraphicsRootSignature(root_signature.Get()); // set the root signature
command_list->RSSetViewports(1, &viewport); // set the viewports
command_list->RSSetScissorRects(1, &scissor_rect); // set the scissor rects
command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // set the primitive topology
command_list->IASetVertexBuffers(0, 1, &vertex_buffer_view); // set the vertex buffer (using the vertex buffer view)
command_list->DrawInstanced(3, 1, 0, 0); // finally draw 3 vertices (draw the triangle)

barrier = CD3DX12_RESOURCE_BARRIER::Transition(render_targets[frame_index].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
command_list->ResourceBarrier(1, &barrier);

I have tried adding a fence after creating the resources and waiting for that fence before calling DrawInstanced(), in case it's still making them once it's started drawing. I tried moving where I call create_vertex_buffers().

I'm unsure of when I should be creating these buffers. I'm planning on later only creating them when necessary, which will be every so often, not every frame.

As you can see my command list does the following in order:

  1. Empties (or at least tries to) and re-creates my default and upload heaps for vertex buffering
  2. Sets the render targets + depth stencil
  3. Clears the render target view
  4. Clears the depth stencil view
  5. Sets the pipeline state
  6. Sets the root signature
  7. Sets the viewport(s)
  8. Sets the scissor rect(s)
  9. Sets the primitive topology
  10. Sets the vertex buffer(s)
  11. Draws

Without going through your code in detail, you usually get that error when you are deleting some resource that's still being used by the GPU. Since you said “re-create”, the problem may be that your previous buffers are still in use, and you are trying to delete them. Since I create and delete meshes constantly for my LOD system, this was a big issue for me.

I ended up creating a reference counting system that saves references in a frame specific array, to each mesh being displayed in that frame. The array is cleared at the end of the frame (i.e. when the frame is finished rendering, as marked by a fence). If there are no other references to a mesh after the frame is finished, that mesh is handed off to a mesh graveyard thread, which deletes it outside of the render thread as not to slow rendering down.

This might be overkill for you, but you need some mechanism to make sure any resources used by commands on the queue (in flight commands) are somehow protected from being deleted until after all commands that use them are executed.

Advertisement

@Gnollrunner I have now tried calling vertex_buffer.Reset() (it is a ComPtr) right after I call wait_for_previous_frame() at the very beginning of update_pipeline() (which is the function that the second snippet is derived from.)

My new process (a single thread for the moment) is:

  1. Wait for the end of the last frame via a fence (wait_for_previous_frame())
  2. Destroy vertex buffer (vertex_buffer.Reset())
  3. Reset the command allocator and command list
  4. Create new vertex buffers (create_vertex_buffers())
  5. Set render target(s)
  6. Clear rtv and dsv
  7. Set pso, root signature, viewport(s), scissor rect(s), primitive topology, and vertex buffers
  8. Draw
  9. Close command list
  10. Execute command list(s)
  11. Signal fence
  12. Present
  13. Repeat

I would think this would work because after waiting for the end of the frame, I start destroying the vertex buffer only to recreate it before I do anything with it; D3D12 still disagrees with the same error.

My call stack shows that it crashes when I call ID3D12CommandAllocator::Reset().

Without actually debugging the code, you might try flipping 2 and 3 around. It might be fooled into thinking the commands are still in use since you destroy the vertexes before resetting the allocator. As a side note, you don't have to wait until the end of the frame to reset the command list. You can do that right after submitting it, and reuse it for the next frame. The command allocator you do have to wait for however so leave that until after the fence.

Which brings up my next question. How many inflight frames are you supporting? Unless you are early in Luna's book, he supports several inflight frames as do most examples out on the internet. In practice you typically don't need more than 2 or 3. However if you are doing multiple frames, resetting the command allocator at the end of each frame is going to be a problem. What I do is have a separate allocator for each possible in-flight frame and only reset the one associated with the frame that just finished. I think Luna does something similar, but I don't remember off hand.

DX12 can be pretty tricky to get your head around. It took me months to come up with an interface for my engine that I felt comfortable with. But once you get past that point it's not so bad.

Edit: BTW In regard to the title of your post, creating buffers should never be a problem. I create vertex and index buffers all over the place and mostly in separate threads while the rendering is going on. I then hand those off to the render thread when they are ready. It's never been an issue. It even works fine in DX11.

This topic is closed to new replies.

Advertisement