Files
Juliet/Juliet/render/d3d12/render_d3d12.c
T

412 lines
17 KiB
C

// TODO : Adapt stuff from D3D12GraphicsDevice.c specially all the shenanigans with dxgi factory and stuff
// TODO : Support more kind of format (HDR)
// =============================================================================
// Helpers
internal R_D3D12_Window* r_d3d12_window_from_handle(R_Handle handle)
{
R_D3D12_Window* window = (R_D3D12_Window*)handle.u64[0];
if (window == 0)
{
window = &r_d3d12_nil_window;
}
return window;
}
internal R_Handle r_d3d12_handle_from_window(R_D3D12_Window* window)
{
R_Handle handle = { 0 };
handle.u64[0] = (uint64)window;
return handle;
}
// =============================================================================
// Wait Function
internal void r_d3d12_wait()
{
r_d3d12_state->global_fence_value += 1;
ID3D12CommandQueue_Signal(r_d3d12_state->direct_command_queue, r_d3d12_state->fence, r_d3d12_state->global_fence_value);
r_d3d12_wait_for_fence_value(r_d3d12_state->global_fence_value);
}
internal void r_d3d12_wait_for_fence_value(uint64 fence_value)
{
if (ID3D12Fence_GetCompletedValue(r_d3d12_state->fence) < fence_value)
{
ID3D12Fence_SetEventOnCompletion(r_d3d12_state->fence, fence_value, r_d3d12_state->fence_event);
WaitForSingleObject(r_d3d12_state->fence_event, INFINITE);
}
}
// =============================================================================
// top level layer initialization
void r_init()
{
HRESULT error = 0;
Arena* arena = arena_alloc();
r_d3d12_state = push_array(arena, R_D3D12_State, 1);
r_d3d12_state->arena = arena;
r_d3d12_state->device_rw_mutex = rw_mutex_alloc();
// Create base device
D3D_FEATURE_LEVEL feature_level = D3D_FEATURE_LEVEL_12_1;
UINT dxgi_flags = 0;
#if BUILD_DEBUG
ID3D12Debug1* debug_controller = 0;
if (SUCCEEDED(D3D12GetDebugInterface(&IID_ID3D12Debug1, (void**)&debug_controller)))
{
ID3D12Debug1_EnableDebugLayer(debug_controller);
ID3D12Debug1_SetEnableGPUBasedValidation(debug_controller, TRUE);
ID3D12Debug1_Release(debug_controller);
}
dxgi_flags |= DXGI_CREATE_FACTORY_DEBUG;
#endif
error = CreateDXGIFactory2(dxgi_flags, &IID_IDXGIFactory4, (void**)&r_d3d12_state->dxgi_factory);
if (FAILED(error))
{
char buffer[256] = { 0 };
juliet_snprintf(buffer, sizeof(buffer), "D3D12 DXGI factory creation failure (%lx). Process is terminating", error);
sh_message(1, str8_literal("Fatal Error"), str8_cstring(buffer));
abort_self(1);
}
// dxgi setup (device and adapter)
IDXGIFactory6* factory6 = 0;
error = IDXGIFactory4_QueryInterface(r_d3d12_state->dxgi_factory, &IID_IDXGIFactory6, (void**)&factory6);
if (SUCCEEDED(error))
{
// Prioritize discrete GPU
error = IDXGIFactory6_EnumAdapterByGpuPreference(factory6, 0, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
&IID_IDXGIAdapter1, (void**)&r_d3d12_state->dxgi_adapter);
IDXGIFactory6_Release(factory6);
}
else
{
// Pray device 0 is the discrete gpu
error = IDXGIFactory4_EnumAdapters1(r_d3d12_state->dxgi_factory, 0, &r_d3d12_state->dxgi_adapter);
}
error = D3D12CreateDevice((IUnknown*)r_d3d12_state->dxgi_adapter, feature_level, &IID_ID3D12Device4,
(void**)&r_d3d12_state->device);
// Try warp as hardware is no available
if (FAILED(error))
{
IDXGIAdapter1_Release(r_d3d12_state->dxgi_adapter);
error = IDXGIFactory4_EnumWarpAdapter(r_d3d12_state->dxgi_factory, &IID_IDXGIAdapter, (void**)&r_d3d12_state->dxgi_adapter);
if (SUCCEEDED(error))
{
error = D3D12CreateDevice((IUnknown*)r_d3d12_state->dxgi_adapter, feature_level, &IID_ID3D12Device4,
(void**)&r_d3d12_state->device);
}
}
if (FAILED(error))
{
char buffer[256] = { 0 };
juliet_snprintf(buffer, sizeof(buffer), "D3D12 device factory creation failure (%lx). Process is terminating", error);
sh_message(1, str8_literal("Fatal Error"), str8_cstring(buffer));
abort_self(1);
}
// break on error
#if BUILD_DEBUG
ID3D12InfoQueue* info = 0;
error = ID3D12Device4_QueryInterface(r_d3d12_state->device, &IID_ID3D12InfoQueue, (void**)&info);
if (SUCCEEDED(error))
{
error = ID3D12InfoQueue_SetBreakOnSeverity(info, D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
error = ID3D12InfoQueue_SetBreakOnSeverity(info, D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
ID3D12InfoQueue_Release(info);
}
#endif
// Create basic graphics queue
D3D12_COMMAND_QUEUE_DESC queue_desc = { 0 };
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
error = ID3D12Device4_CreateCommandQueue(r_d3d12_state->device, &queue_desc, &IID_ID3D12CommandQueue,
(void**)&r_d3d12_state->direct_command_queue);
if (FAILED(error))
{
char buffer[256] = { 0 };
juliet_snprintf(buffer, sizeof(buffer), "D3D12 direct command queue creation failure (%lx). Process is terminating", error);
sh_message(1, str8_literal("Fatal Error"), str8_cstring(buffer));
abort_self(1);
}
// Create command allocator and command list
for EachIndex(i, 2)
{
ID3D12Device4_CreateCommandAllocator(r_d3d12_state->device, D3D12_COMMAND_LIST_TYPE_DIRECT,
&IID_ID3D12CommandAllocator, (void**)&r_d3d12_state->command_allocators[i]);
}
ID3D12Device4_CreateCommandList1(r_d3d12_state->device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_LIST_FLAG_NONE,
&IID_ID3D12GraphicsCommandList, (void**)&r_d3d12_state->command_list);
ID3D12Device4_CreateFence(r_d3d12_state->device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void**)&r_d3d12_state->fence);
r_d3d12_state->fence_event = CreateEventW(0, FALSE, FALSE, 0);
}
void r_shutdown()
{
MutexScopeW(r_d3d12_state->device_rw_mutex)
{
r_d3d12_wait();
CloseHandle(r_d3d12_state->fence_event);
ID3D12Fence_Release(r_d3d12_state->fence);
for EachIndex(i,2)
{
ID3D12CommandAllocator_Release(r_d3d12_state->command_allocators[i]);
}
ID3D12CommandList_Release(r_d3d12_state->command_list);
ID3D12CommandQueue_Release(r_d3d12_state->direct_command_queue);
ID3D12Device4_Release(r_d3d12_state->device);
IDXGIAdapter1_Release(r_d3d12_state->dxgi_adapter);
IDXGIFactory4_Release(r_d3d12_state->dxgi_factory);
}
rw_mutex_release(r_d3d12_state->device_rw_mutex);
arena_release(r_d3d12_state->arena);
}
// =============================================================================
// window setup/teardown
R_Handle r_window_equip(WM_Window handle)
{
R_Handle result = { 0 };
MutexScopeW(r_d3d12_state->device_rw_mutex)
{
R_D3D12_Window* window = r_d3d12_state->first_free_window;
if (window != 0)
{
SLL_stack_pop(r_d3d12_state->first_free_window);
MemoryZeroStruct(window);
}
else
{
window = push_array(r_d3d12_state->arena, R_D3D12_Window, 1);
}
HWND hwnd = { 0 };
{
W32_WM_Window* w32_layer_window = w32_wm_window_from_handle(handle);
hwnd = w32_wm_hwnd_from_window(w32_layer_window);
}
// Create swapchain
DXGI_SWAP_CHAIN_DESC1 swapchain_desc = { 0 };
swapchain_desc.Width = 0; // Use all window width
swapchain_desc.Height = 0; // Use all window height
swapchain_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapchain_desc.Stereo = 0;
swapchain_desc.SampleDesc.Count = 1;
swapchain_desc.SampleDesc.Quality = 0;
swapchain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapchain_desc.BufferCount = 2; // Double buffering
swapchain_desc.Scaling = DXGI_SCALING_NONE;
swapchain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapchain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
swapchain_desc.Flags = 0;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC swapchain_fullscreen_desc = { 0 };
swapchain_fullscreen_desc.RefreshRate.Numerator = 0;
swapchain_fullscreen_desc.RefreshRate.Denominator = 0;
swapchain_fullscreen_desc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapchain_fullscreen_desc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
swapchain_fullscreen_desc.Windowed = TRUE;
IDXGISwapChain1* swapchain1 = 0;
HRESULT error = IDXGIFactory4_CreateSwapChainForHwnd(r_d3d12_state->dxgi_factory,
(IUnknown*)r_d3d12_state->direct_command_queue, hwnd,
&swapchain_desc, &swapchain_fullscreen_desc, 0, &swapchain1);
if (FAILED(error))
{
char buffer[256];
juliet_snprintf(buffer, sizeof(buffer), "DXGI swap chain creation failure (%lx). Process is terminating.", error);
sh_message(1, str8_literal("Fatal Error"), str8_cstring(buffer));
abort_self(1);
}
IDXGISwapChain1_QueryInterface(swapchain1, &IID_IDXGISwapChain3, (void**)&window->swapchain);
IDXGISwapChain1_Release(swapchain1);
IDXGIFactory4_MakeWindowAssociation(r_d3d12_state->dxgi_factory, hwnd, DXGI_MWA_NO_ALT_ENTER);
// Create framebuffer and rtv for swapchain
D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = { 0 };
rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtv_heap_desc.NumDescriptors = 2;
rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ID3D12Device4_CreateDescriptorHeap(r_d3d12_state->device, &rtv_heap_desc, &IID_ID3D12DescriptorHeap,
(void**)&window->rtv_heap);
UINT rtv_stride = ID3D12Device4_GetDescriptorHandleIncrementSize(r_d3d12_state->device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = { 0 };
ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(window->rtv_heap, &rtv_handle);
for (UINT i = 0; i < 2; ++i)
{
window->rtv_handles[i] = rtv_handle;
IDXGISwapChain3_GetBuffer(window->swapchain, i, &IID_ID3D12Resource, (void**)&window->framebuffer[i]);
ID3D12Device4_CreateRenderTargetView(r_d3d12_state->device, window->framebuffer[i], 0, window->rtv_handles[i]);
rtv_handle.ptr += rtv_stride;
}
// Save resolution in case of resize
Rect2f client_rect = wm_client_rect_from_window(handle);
Vector2i resolution = { (int32)(client_rect.x1 - client_rect.x0), (int32)(client_rect.y1 - client_rect.y0) };
window->last_resolution = resolution;
result = r_d3d12_handle_from_window(window);
}
return result;
}
void r_window_unequip(WM_Window handle, R_Handle handle_equip)
{
MutexScopeW(r_d3d12_state->device_rw_mutex)
{
r_d3d12_wait();
R_D3D12_Window* window = r_d3d12_window_from_handle(handle_equip);
if (window != 0 && window != &r_d3d12_nil_window)
{
for EachIndex(i, 2)
{
ID3D12Resource_Release(window->framebuffer[i]);
}
ID3D12DescriptorHeap_Release(window->rtv_heap);
IDXGISwapChain1_Release(window->swapchain);
SLL_stack_push(r_d3d12_state->first_free_window, window);
}
}
}
// frame markers
void r_begin_frame()
{
MutexScopeW(r_d3d12_state->device_rw_mutex)
{
uint64 frame_slot = r_d3d12_state->frame_idx % 2;
r_d3d12_wait_for_fence_value(r_d3d12_state->frame_fence_value[frame_slot]);
ID3D12CommandAllocator_Reset(r_d3d12_state->command_allocators[frame_slot]);
r_d3d12_state->frame_has_presented = 0;
}
}
void r_end_frame()
{
MutexScopeW(r_d3d12_state->device_rw_mutex)
{
uint64 frame_slot = r_d3d12_state->frame_idx % 2;
// Setup the frame fence
r_d3d12_state->global_fence_value += 1;
ID3D12CommandQueue_Signal(r_d3d12_state->direct_command_queue, r_d3d12_state->fence, r_d3d12_state->global_fence_value);
r_d3d12_state->frame_fence_value[frame_slot] = r_d3d12_state->global_fence_value;
r_d3d12_state->frame_idx += 1;
}
}
void r_window_begin_frame(WM_Window window, R_Handle window_equip)
{
MutexScopeW(r_d3d12_state->device_rw_mutex)
{
R_D3D12_Window* r_window = r_d3d12_window_from_handle(window_equip);
// Get resolution
Rect2f client_rect = wm_client_rect_from_window(window);
Vector2i resolution = { (int32)(client_rect.x1 - client_rect.x0), (int32)(client_rect.y1 - client_rect.y0) };
// change resolution
if (resolution.x > 0 && resolution.y > 0 &&
(r_window->last_resolution.x != resolution.x || r_window->last_resolution.y != resolution.y))
{
r_window->last_resolution = resolution;
// resize swap chain
r_d3d12_wait();
for EachIndex(i,2)
{
if (r_window->framebuffer[i] != 0)
{
ID3D12Resource_Release(r_window->framebuffer[i]);
r_window->framebuffer[i] = 0;
}
}
IDXGISwapChain3_ResizeBuffers(r_window->swapchain, 0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
for EachIndex(i,2)
{
IDXGISwapChain3_GetBuffer(r_window->swapchain, i, &IID_ID3D12Resource, (void**)&r_window->framebuffer[i]);
ID3D12Device4_CreateRenderTargetView(r_d3d12_state->device, r_window->framebuffer[i], 0, r_window->rtv_handles[i]);
}
}
// Open the command list for that window that frame
uint64 frame_slot = r_d3d12_state->frame_idx % 2;
r_window->framebuffer_idx = IDXGISwapChain3_GetCurrentBackBufferIndex(r_window->swapchain);
ID3D12CommandAllocator* allocator = r_d3d12_state->command_allocators[frame_slot];
ID3D12GraphicsCommandList* cmd_list = r_d3d12_state->command_list;
ID3D12GraphicsCommandList1_Reset(cmd_list, allocator, 0);
// Barrier to transition from Present (end of frame) to render target
D3D12_RESOURCE_BARRIER barrier = { 0 };
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = r_window->framebuffer[r_window->framebuffer_idx];
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
ID3D12GraphicsCommandList_ResourceBarrier(cmd_list, 1, &barrier);
// HARDCODED
// BLUE
float32 background[4] = { 0.1f, 0.2f, 0.8f, 1.0f };
ID3D12GraphicsCommandList_ClearRenderTargetView(cmd_list, r_window->rtv_handles[r_window->framebuffer_idx],
background, 0, 0);
}
}
void r_window_end_frame(WM_Window window, R_Handle window_equip)
{
MutexScopeW(r_d3d12_state->device_rw_mutex)
{
R_D3D12_Window* r_window = r_d3d12_window_from_handle(window_equip);
ID3D12GraphicsCommandList* cmd_list = r_d3d12_state->command_list;
// Transition from render target to present
D3D12_RESOURCE_BARRIER barrier = { 0 };
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = r_window->framebuffer[r_window->framebuffer_idx];
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
ID3D12GraphicsCommandList_ResourceBarrier(cmd_list, 1, &barrier);
// Close command list and push it onto the command queue
ID3D12GraphicsCommandList_Close(cmd_list);
ID3D12CommandQueue_ExecuteCommandLists(r_d3d12_state->direct_command_queue, 1, (ID3D12CommandList**)&cmd_list);
UINT sync_interval = r_d3d12_state->frame_has_presented ? 0 : 1;
IDXGISwapChain3_Present(r_window->swapchain, sync_interval, 0);
r_d3d12_state->frame_has_presented = 1;
}
}