From a73b6cdd35484400708f3c565618de25b347ea8c Mon Sep 17 00:00:00 2001 From: ShironekoBen <47202645+ShironekoBen@users.noreply.github.com> Date: Fri, 22 May 2020 16:08:24 +0900 Subject: [PATCH] Added DX12 sample --- Image-Loading-and-Displaying-Examples.md | 223 +++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/Image-Loading-and-Displaying-Examples.md b/Image-Loading-and-Displaying-Examples.md index 0daf4d5..e8c3224 100644 --- a/Image-Loading-and-Displaying-Examples.md +++ b/Image-Loading-and-Displaying-Examples.md @@ -225,6 +225,229 @@ ImGui::End(); ---- +## Example for DirectX12 users + +It should be noted that the DirectX12 API is substantially more complex than previous iterations, and assumes that you will be writing your own code to manage various things such as resource allocation. As such, it's a lot harder to provide a "one size fits all" example than with some APIs, and in all probability the code below will need modification to work with any existing engine code. It should however work if inserted into the Dear ImGui DirectX12 example project, with the caveat that the `CreateDeviceD3D()` function will need to be modified slightly to allocate two descriptors in the SRV resource descriptor heap as follows: + +```C++ +{ + D3D12_DESCRIPTOR_HEAP_DESC desc = {}; + desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + desc.NumDescriptors = 2; // <-- Set this value to 2 (the first descriptor is used for the built-in font texture, the second for our new texture) + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dSrvDescHeap)) != S_OK) + return false; +} +``` + +If you want to use this code to load more than one texture (and don't have your own mechanism for allocating descriptors), then you will need to increase `NumDescriptors` accordingly and also increment `descriptor_index` for each texture loaded. + +The actual implementation itself is as follows - firstly, at the top of one of your source files add: +```C++ +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +// Simple helper function to load an image into a DX12 texture with common settings +// Returns true on success, with the SRV CPU handle having an SRV for the newly-created texture placed in it (srv_cpu_handle must be a handle in a valid descriptor heap) +bool LoadTextureFromFile(const char* filename, ID3D12Device* d3d_device, D3D12_CPU_DESCRIPTOR_HANDLE srv_cpu_handle, ID3D12Resource** out_tex_resource, int* out_width, int* out_height) +{ + // Load from disk into a raw RGBA buffer + int image_width = 0; + int image_height = 0; + unsigned char* image_data = stbi_load(filename, &image_width, &image_height, NULL, 4); + if (image_data == NULL) + return false; + + // Create texture resource + D3D12_HEAP_PROPERTIES props; + memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES)); + props.Type = D3D12_HEAP_TYPE_DEFAULT; + props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + + D3D12_RESOURCE_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + desc.Alignment = 0; + desc.Width = image_width; + desc.Height = image_height; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + desc.Flags = D3D12_RESOURCE_FLAG_NONE; + + ID3D12Resource* pTexture = NULL; + d3d_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, + D3D12_RESOURCE_STATE_COPY_DEST, NULL, IID_PPV_ARGS(&pTexture)); + + // Create a temporary upload resource to move the data in + UINT uploadPitch = (image_width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); + UINT uploadSize = image_height * uploadPitch; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Alignment = 0; + desc.Width = uploadSize; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = D3D12_RESOURCE_FLAG_NONE; + + props.Type = D3D12_HEAP_TYPE_UPLOAD; + props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + + ID3D12Resource* uploadBuffer = NULL; + HRESULT hr = d3d_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&uploadBuffer)); + IM_ASSERT(SUCCEEDED(hr)); + + // Write pixels into the upload resource + void* mapped = NULL; + D3D12_RANGE range = { 0, uploadSize }; + hr = uploadBuffer->Map(0, &range, &mapped); + IM_ASSERT(SUCCEEDED(hr)); + for (int y = 0; y < image_height; y++) + memcpy((void*)((uintptr_t)mapped + y * uploadPitch), image_data + y * image_width * 4, image_width * 4); + uploadBuffer->Unmap(0, &range); + + // Copy the upload resource content into the real resource + D3D12_TEXTURE_COPY_LOCATION srcLocation = {}; + srcLocation.pResource = uploadBuffer; + srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srcLocation.PlacedFootprint.Footprint.Width = image_width; + srcLocation.PlacedFootprint.Footprint.Height = image_height; + srcLocation.PlacedFootprint.Footprint.Depth = 1; + srcLocation.PlacedFootprint.Footprint.RowPitch = uploadPitch; + + D3D12_TEXTURE_COPY_LOCATION dstLocation = {}; + dstLocation.pResource = pTexture; + dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLocation.SubresourceIndex = 0; + + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = pTexture; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + + // Create a temporary command queue to do the copy with + ID3D12Fence* fence = NULL; + hr = d3d_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); + IM_ASSERT(SUCCEEDED(hr)); + + HANDLE event = CreateEvent(0, 0, 0, 0); + IM_ASSERT(event != NULL); + + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queueDesc.NodeMask = 1; + + ID3D12CommandQueue* cmdQueue = NULL; + hr = d3d_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&cmdQueue)); + IM_ASSERT(SUCCEEDED(hr)); + + ID3D12CommandAllocator* cmdAlloc = NULL; + hr = d3d_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); + IM_ASSERT(SUCCEEDED(hr)); + + ID3D12GraphicsCommandList* cmdList = NULL; + hr = d3d_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, NULL, IID_PPV_ARGS(&cmdList)); + IM_ASSERT(SUCCEEDED(hr)); + + cmdList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, NULL); + cmdList->ResourceBarrier(1, &barrier); + + hr = cmdList->Close(); + IM_ASSERT(SUCCEEDED(hr)); + + // Execute the copy + cmdQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmdList); + hr = cmdQueue->Signal(fence, 1); + IM_ASSERT(SUCCEEDED(hr)); + + // Wait for everything to complete + fence->SetEventOnCompletion(1, event); + WaitForSingleObject(event, INFINITE); + + // Tear down our temporary command queue and release the upload resource + cmdList->Release(); + cmdAlloc->Release(); + cmdQueue->Release(); + CloseHandle(event); + fence->Release(); + uploadBuffer->Release(); + + // Create a shader resource view for the texture + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + d3d_device->CreateShaderResourceView(pTexture, &srvDesc, srv_cpu_handle); + + // Return results + *out_tex_resource = pTexture; + *out_width = image_width; + *out_height = image_height; + stbi_image_free(image_data); + + return true; +} +``` + +Then at initialization time, load our texture: +```C++ +// We need to pass a D3D12_CPU_DESCRIPTOR_HANDLE in ImTextureID, so make sure it will fit +static_assert(sizeof(ImTextureID) >= sizeof(D3D12_CPU_DESCRIPTOR_HANDLE), "D3D12_CPU_DESCRIPTOR_HANDLE is too large to fit in an ImTextureID"); + +// We presume here that we have our D3D device pointer in g_pd3dDevice + +int my_image_width = 0; +int my_image_height = 0; +ID3D12Resource* my_texture = NULL; + +// Get CPU/GPU handles for the shader resource view +// Normally your engine will have some sort of allocator for these - here we assume that there's an SRV descriptor heap in +// g_pd3dSrvDescHeap with at least two descriptors allocated, and descriptor 1 is unused +UINT handle_increment = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); +int descriptor_index = 1; // The descriptor table index to use (not normally a hard-coded constant, but in this case we'll assume we have slot 1 reserved for us) +D3D12_CPU_DESCRIPTOR_HANDLE my_texture_srv_cpu_handle = g_pd3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(); +my_texture_srv_cpu_handle.ptr += (handle_increment * descriptor_index); +D3D12_GPU_DESCRIPTOR_HANDLE my_texture_srv_gpu_handle = g_pd3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart(); +my_texture_srv_gpu_handle.ptr += (handle_increment * descriptor_index); + +// Load the texture from a file +bool ret = LoadTextureFromFile("../../MyImage01.jpg", g_pd3dDevice, my_texture_srv_cpu_handle, &my_texture, &my_image_width, &my_image_height); +IM_ASSERT(ret); +``` + +And finally now that we have an DirectX12 texture and its dimensions, we can display it in our main loop: +```C++ +ImGui::Begin("DirectX12 Texture Test"); +ImGui::Text("CPU handle = %p", my_texture_srv_cpu_handle.ptr); +ImGui::Text("GPU handle = %p", my_texture_srv_gpu_handle.ptr); +ImGui::Text("size = %d x %d", my_image_width, my_image_height); +// Note that we pass the GPU SRV handle here, *not* the CPU handle. We're passing the internal pointer value, cast to an ImTextureID +ImGui::Image((ImTextureID)my_texture_srv_gpu_handle.ptr, ImVec2((float)my_image_width, (float)my_image_height)); +ImGui::End(); +``` + +![Sample screenshot](https://user-images.githubusercontent.com/47202645/82640130-05ab2480-9c45-11ea-9488-82b8554db638.jpg) + +---- + ### Technical Explanation From the FAQ: "How can I display an image? What is ImTextureID, how does it works?"