Created
June 24, 2016 19:01
-
-
Save sherief/17aae77a8eee366f39c93e104fcfb3dc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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