Skip to content

Instantly share code, notes, and snippets.

@sherief
Created June 24, 2016 19:01
Show Gist options
  • Select an option

  • Save sherief/17aae77a8eee366f39c93e104fcfb3dc to your computer and use it in GitHub Desktop.

Select an option

Save sherief/17aae77a8eee366f39c93e104fcfb3dc to your computer and use it in GitHub Desktop.
class copy_command_list //wraps ID3D12GraphicsCommandList instances of the COPY type variety.
//Why this is needed will be made clear later
{
public:
void CopyBufferRegion(...);
void CopyResource(...);
void CopyTextureRegion(...);
void ResourceBarrier(...);
void Close(); //I'll explain why this returns void and not an HRESULT - it's intentional.
};
class dispatch_manager //Handles firing off async tasks.
{
public:
enum class async_fn_flags : unsigned int
{
IO = (1 << 0),
//other access flags
};
enum class async_fn_priority : unsigned int
{
high, medium, low
};
typedef void(*async_function)(void* const UserContext);
typedef void(*async_copy_function)(copy_command_list* const CopyCommandList, void* const UserContext);
//Actual member functions that do the dispatching
//This executes AsyncFunction(), passing it AsyncFunctionContext as its sole parameter, on a background thread.
void dispatch_async(async_function AsyncFunction, void* const AsyncFunctionContext, async_fn_flags Flags, async_fn_priority Priority = async_fn_priority::medium);
//This executes AsyncCopyFunction on a background thread, providing it with an engine-managed copy_command_list instance and the user-provided context
//After the commands added to the copy command list are retired by the GPU, in an unspecified time in the future, the engine calls the completion callback.
void dispatch_copy_async(async_copy_function AsyncCopyFunction, void* const AsyncCopyFunctionUserContext, async_function CompletionCallback, void* const CompletionCallbackContext);
};
dispatch_manager* DispatchManager;
void texture::load()
{
this->State = state::loading;
DispatchManager->dispatch_async(async_load_begin, this);
}
void async_load_begin(void* const UserContext)
{
texture* const Texture = (texture* const)UserContext;
int width, height, component_count;
void* data;
data = load_png_from_file(&width, &height, &count);
D3D12_RESOURCE_DESC ResourceDescription = { ... };
//
staging_allocation UploadAllocation = Renderer->allocate_staging_area(...);
gpu_allocation DefaultHeapAllocation = TargetHeap->allocate(...);
//Copy to upload heap
memcpy(UploadAllocation.pointer, data, size);
free(data);
//Create texture resource
D3D12Device->CreatePlacedResource(...);
//Fill contexts
copy_async_context CopyAsyncContext;
CopyAsyncContext.TextureResource = Texture;
CopyAsyncContext.StagingBufferAllocation = UploadAllocation;
CopyAsyncContext.SizeInBytes = SizeInBytes;
CopyAsyncContext.width = width;
CopyAsyncContext.height = height;
//
completion_context CompletionContext;
CompletionContext.TextureObject = this;
CompletionContext.event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
CompletionContext.Renderer = Renderer;
CompletionContext.StagingBufferAllocation = UploadAllocation;
//Fire off async copy
DispatchManager->dispatch_copy_async(copy_async, &CopyAsyncContext, completion_callback, &CompletionContext); //Returns immediately
#if YOU_WANT_TO_STILL_LOAD_SYNCHRONOUSLY
//Wait for async copy to finish
const auto xWaitResult = WaitForSingleObject(CompletionContext.event, INFINITE);
assert(xWaitResult == WAIT_OBJECT_0);
CloseHandle(CompletionContext.event);
#endif
}
//Now for the part that schedules the actual GPU copy
struct copy_async_context
{
ComPtr<ID3D12Resource> TextureResource;
renderer::staging_allocation StagingBufferAllocation;
size_t SizeInBytes;
size_t width;
size_t height;
};
//This is executed once the dispatch manager sets up a copy command list.
//Callbacks are called on *ANY* background threads, not the ones they were launched from.
void copy_async(ID3D12GraphicsCommandList* const CopyCommandList, void* const UserContext)
{
copy_async_context* const Context = (copy_async_context* const)UserContext;
//
D3D12_TEXTURE_COPY_LOCATION SourceLocation = { ... };
//
D3D12_TEXTURE_COPY_LOCATION DestinationLocation = { ... };
//
CopyCommandList->CopyTextureRegion(&DestinationLocation, 0, 0, 0, &SourceLocation, nullptr);
//Note that the engine provides a copy command list, it's not created by the callback.
//Ideal use of command lists and managing allocators needs more context
//Ideally you would use the same allocator for a similar size of copy commands in order not to end up with worst-case size allocators.
//Passing a subclass that wrap the actual ID3D12GraphicsCommandList allows us to track the number and byte size of copy commands made.
//This way the engine can use better heuristics to determine when to cycle command allocators (the copy command queue is independent of the "frames" concept)
//This copy async function can also return a struct with some data like priority or latency tolerated for this copy to finish.
//This can help ensure better heuristics and copy queue utilization.
//
//Apply the proper barriers to move it to the COMMON state on the copy queue
//This enables the use of implicit promotions to use it in the pixel shader resource state on a 3D / Compute queue.
D3D12_RESOURCE_BARRIER Barrier;
Barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
Barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
Barrier.Transition.pResource = Context->TextureResource.Get();
Barrier.Transition.Subresource = 0;
Barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
Barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
CopyCommandList->ResourceBarrier(1, &Barrier);
//Now why does Close() return void? In out ID3D12GraphicsCommandList wrapper, Close() is merely a hint.
//If we decide to keep the command list open to, say, merge multiple copy commands into one list to avoid tiny list submissions, we can do that.
//There could also be a debug flag that says (Close() means Close()!) so you can get errors ASAP and pinpoint callbacks causing them.
CopyCommandList->Close();
}
//and finally, the part that handles the completion of the copy.
//This is called AFTER the GPU copy has finished executing, as indicated by a fence.
//Callbacks are called on *ANY* background threads, not the ones they were launched from.
struct completion_context
{
texture* TextureObject;
HANDLE event;
renderer* Renderer;
renderer::staging_allocation StagingBufferAllocation;
};
void completion_callback(void* const UserCompletionContext)
{
completion_context* const Context = (completion_context* const)UserCompletionContext;
Context->TextureObject->State = state::loaded;
SetEvent(Context->event);
Context->Renderer->release_staging_area(Context->StagingBufferAllocation);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment