412 lines
17 KiB
C
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;
|
|
}
|
|
}
|