power_play/src/gpu_dx12.c
2025-06-06 22:36:09 -05:00

572 lines
16 KiB
C

#if DX12_TEST
#include "gpu.h"
#include "sys.h"
#include "arena.h"
#include "memory.h"
#include "string.h"
#include "scratch.h"
#include "app.h"
#pragma warning(push, 0)
# define UNICODE
# define COBJMACROS
# include <Windows.h>
# include <d3d12.h>
# include <dxgidebug.h>
# include <dxgi1_6.h>
# include <combaseapi.h>
#pragma warning(pop)
#pragma comment(lib, "d3d12")
#pragma comment(lib, "dxgi")
#pragma comment(lib, "dxguid")
//#define DX12_WAIT_FRAME_LATENCY 1
//#define DX12_ALLOW_TEARING 1
#define DX12_SWAPCHAIN_BUFFER_COUNT (3)
#define DX12_SWAPCHAIN_FLAGS ((DX12_ALLOW_TEARING * DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) | (DX12_WAIT_FRAME_LATENCY * DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT))
#define DX12_SWAPCHAIN_FORMAT (DXGI_FORMAT_R8G8B8A8_UNORM)
//#define DX12_SWAPCHAIN_RTV_FORMAT (DXGI_FORMAT_R8G8B8A8_UNORM_SRGB)
#if RTC
# define DX12_DEBUG 1
# define DX12_SHADER_DEBUG 1
#else
# define DX12_DEBUG 0
# define DX12_SHADER_DEBUG 0
#endif
enum dx12_handle_kind {
DX12_HANDLE_KIND_NONE,
DX12_HANDLE_KIND_TEXTURE,
NUM_DX12_HANDLE_KINDS
};
struct dx12_handle_entry {
enum dx12_handle_kind kind;
u64 gen;
u64 idx;
void *data;
struct dx12_handle_entry *next_free;
};
struct dx12_texture {
i32 _;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
/* Handles pool */
struct sys_mutex handle_entries_mutex;
struct arena handle_entries_arena;
struct dx12_handle_entry *first_free_handle_entry;
u64 num_handle_entries_reserved;
/* Device */
ID3D12Device *device;
/* Desc sizes */
u32 desc_size_rtv;
/* Command queues */
ID3D12CommandQueue *cq_direct;
ID3D12CommandQueue *cq_compute;
/* Swapchain */
u32 swapchain_frame_index;
ID3D12CommandAllocator *swapchain_ca;
IDXGISwapChain3 *swapchain;
ID3D12DescriptorHeap *swapchain_rtv_heap;
ID3D12Resource *swapchain_rtvs[DX12_SWAPCHAIN_BUFFER_COUNT];
} G = ZI, DEBUG_ALIAS(G, G_gpu_dx12);
/* ========================== *
* Startup
* ========================== */
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(gpu_shutdown);
INTERNAL void dx12_init_base(struct sys_window *window);
INTERNAL void dx12_init_shaders(void);
struct gpu_startup_receipt gpu_startup(struct sys_window *window)
{
/* Initialize handles pool */
G.handle_entries_mutex = sys_mutex_alloc();
G.handle_entries_arena = arena_alloc(GIGABYTE(64));
/* Initialize dx12 */
dx12_init_base(window);
dx12_init_shaders();
/* Register callbacks */
app_register_exit_callback(gpu_shutdown);
struct gpu_startup_receipt res = ZI;
return res;
}
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(gpu_shutdown)
{
#if DX12_DEBUG
/* Release objects to make live object reporting less noisy */
for (u64 i = 0; i < ARRAY_COUNT(G.swapchain_rtvs); ++i) {
ID3D12Resource_Release(G.swapchain_rtvs[i]);
}
ID3D12DescriptorHeap_Release(G.swapchain_rtv_heap);
ID3D12CommandQueue_Release(G.swapchain_ca);
ID3D12CommandQueue_Release(G.cq_direct);
ID3D12CommandQueue_Release(G.cq_compute);
IDXGISwapChain3_Release(G.swapchain);
ID3D12Device_Release(G.device);
#endif
}
/* ========================== *
* Dx12 base initialization
* ========================== */
INTERNAL void dx12_init_error(struct string error)
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct string msg = string_format(scratch.arena, LIT("Failed to initialize DirectX 12.\n\n%F"), FMT_STR(error));
sys_panic(msg);
scratch_end(scratch);
}
INTERNAL void dx12_init_base(struct sys_window *window)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
HRESULT hr = 0;
/* Enable debug layer */
u32 dxgi_factory_flags = 0;
#if DX12_DEBUG
{
ID3D12Debug *debug_controller0 = NULL;
hr = D3D12GetDebugInterface(&IID_ID3D12Debug, (void **)&debug_controller0);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create ID3D12Debug0"));
}
ID3D12Debug1 *debug_controller1 = NULL;
hr = ID3D12Debug_QueryInterface(debug_controller0, &IID_ID3D12Debug1, (void **)&debug_controller1);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create ID3D12Debug1"));
}
ID3D12Debug_EnableDebugLayer(debug_controller0);
/* FIXME: Enable this */
//ID3D12Debug1_SetEnableGPUBasedValidation(debug_controller1, true);
ID3D12Debug_Release(debug_controller1);
ID3D12Debug_Release(debug_controller0);
dxgi_factory_flags |= DXGI_CREATE_FACTORY_DEBUG;
}
#endif
/* Create factory */
IDXGIFactory6 *factory = NULL;
hr = CreateDXGIFactory2(dxgi_factory_flags, &IID_IDXGIFactory6, (void **)&factory);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to initialize DXGI factory"));
}
/* Create device */
ID3D12Device *device = NULL;
{
struct string error = LIT("Could not initialize GPU device.");
struct string first_gpu_name = ZI;
u32 adapter_index = 0;
while (true) {
IDXGIAdapter1 *adapter = NULL;
hr = IDXGIFactory6_EnumAdapterByGpuPreference(factory, adapter_index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, &IID_IDXGIAdapter1, (void **)&adapter);
if (SUCCEEDED(hr)) {
DXGI_ADAPTER_DESC1 desc;
IDXGIAdapter1_GetDesc1(adapter, &desc);
if (!(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) {
if (first_gpu_name.len == 0) {
first_gpu_name = string_from_wstr_no_limit(scratch.arena, desc.Description);
}
hr = D3D12CreateDevice((IUnknown *)adapter, D3D_FEATURE_LEVEL_12_0, &IID_ID3D12Device, (void **)&device);
if (SUCCEEDED(hr)) {
IDXGIAdapter1_Release(adapter);
adapter = NULL;
break;
}
}
ID3D12Device_Release(device);
IDXGIAdapter1_Release(adapter);
adapter = NULL;
device = NULL;
++adapter_index;
} else {
break;
}
}
if (!device) {
if (first_gpu_name.len > 0) {
struct string fmt = LIT("Could not initialize device '%F' with D3D_FEATURE_LEVEL_12_0. Ensure that the device is capable and drivers are up to date.");
error = string_format(scratch.arena, fmt, FMT_STR(first_gpu_name));
}
dx12_init_error(error);
}
}
/* Get desc sizes */
u32 desc_size_rtv = ID3D12Device_GetDescriptorHandleIncrementSize(device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
#if DX12_DEBUG
/* Enable D3D12 Debug break */
{
ID3D12InfoQueue *info = NULL;
hr = ID3D12Device_QueryInterface(device, &IID_ID3D12InfoQueue, (void **)&info);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to query ID3D12Device interface"));
}
ID3D12InfoQueue_SetBreakOnSeverity(info, D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
ID3D12InfoQueue_SetBreakOnSeverity(info, D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
ID3D12InfoQueue_Release(info);
}
/* Enable DXGI Debug break */
{
IDXGIInfoQueue *dxgi_info = NULL;
hr = DXGIGetDebugInterface1(0, &IID_IDXGIInfoQueue, (void **)&dxgi_info);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to get DXGI debug interface"));
}
IDXGIInfoQueue_SetBreakOnSeverity(dxgi_info, DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, TRUE);
IDXGIInfoQueue_SetBreakOnSeverity(dxgi_info, DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, TRUE);
IDXGIInfoQueue_Release(dxgi_info);
}
#endif
/* Create direct command queue */
ID3D12CommandQueue *cq_direct = NULL;
{
D3D12_COMMAND_QUEUE_DESC desc = ZI;
desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
hr = ID3D12Device_CreateCommandQueue(device, &desc, &IID_ID3D12CommandQueue, (void **)&cq_direct);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create direct command queue"));
}
}
/* Create compute command queue */
ID3D12CommandQueue *cq_compute = NULL;
{
D3D12_COMMAND_QUEUE_DESC desc = ZI;
desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
desc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
hr = ID3D12Device_CreateCommandQueue(device, &desc, &IID_ID3D12CommandQueue, (void **)&cq_compute);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create compute command queue"));
}
}
/* Create swapchain command allocator */
ID3D12CommandAllocator *swapchain_ca = NULL;
{
hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_DIRECT, &IID_ID3D12CommandAllocator, (void **)&swapchain_ca);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create swapchain command allocator"));
}
}
/* Create swapchain */
IDXGISwapChain3 *swapchain = NULL;
u32 swapchain_frame_index = 0;
{
HWND hwnd = (HWND)sys_window_get_internal_handle(window);
DXGI_SWAP_CHAIN_DESC1 desc = {
.Format = DX12_SWAPCHAIN_FORMAT,
.SampleDesc = { 1, 0 },
.BufferUsage = DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = DX12_SWAPCHAIN_BUFFER_COUNT,
.Scaling = DXGI_SCALING_NONE,
.Flags = DX12_SWAPCHAIN_FLAGS,
.AlphaMode = DXGI_ALPHA_MODE_IGNORE,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD
};
/* Create swapchain1 */
IDXGISwapChain1 *swapchain1 = NULL;
hr = IDXGIFactory2_CreateSwapChainForHwnd(factory, (IUnknown *)cq_direct, hwnd, &desc, NULL, NULL, &swapchain1);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create IDXGISwapChain1"));
}
/* Upgrade to swapchain3 */
hr = IDXGISwapChain1_QueryInterface(swapchain1, &IID_IDXGISwapChain3, (void **)&swapchain);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create IDXGISwapChain3"));
}
/* Disable Alt+Enter changing monitor resolution to match window size */
IDXGIFactory_MakeWindowAssociation(factory, hwnd, DXGI_MWA_NO_ALT_ENTER);
/* Get initial frame index */
swapchain_frame_index = IDXGISwapChain3_GetCurrentBackBufferIndex(swapchain);
IDXGISwapChain1_Release(swapchain1);
}
/* Create swapchain RTV heap */
ID3D12DescriptorHeap *swapchain_rtv_heap = NULL;
{
D3D12_DESCRIPTOR_HEAP_DESC desc = ZI;
desc.NumDescriptors = DX12_SWAPCHAIN_BUFFER_COUNT;
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
hr = ID3D12Device_CreateDescriptorHeap(device, &desc, &IID_ID3D12DescriptorHeap, (void **)&swapchain_rtv_heap);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create swapchain RTV heap"));
}
}
/* Create swacphain RTVs */
ID3D12Resource *swapchain_rtvs[DX12_SWAPCHAIN_BUFFER_COUNT] = ZI;
{
D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = ZI;
ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(swapchain_rtv_heap, &rtv_handle);
for (u32 i = 0; i < DX12_SWAPCHAIN_BUFFER_COUNT; ++i) {
hr = IDXGISwapChain3_GetBuffer(swapchain, i, &IID_ID3D12Resource, (void **)&swapchain_rtvs[i]);
if (FAILED(hr)) {
dx12_init_error(LIT("Failed to create swapchain RTV"));
}
ID3D12Device_CreateRenderTargetView(device, swapchain_rtvs[i], NULL, rtv_handle);
rtv_handle.ptr += desc_size_rtv;
}
}
G.device = device;
G.desc_size_rtv = desc_size_rtv;
G.swapchain_frame_index = swapchain_frame_index;
G.cq_direct = cq_direct;
G.cq_compute = cq_compute;
G.swapchain_ca = swapchain_ca;
G.swapchain = swapchain;
G.swapchain_rtv_heap = swapchain_rtv_heap;
MEMCPY(&G.swapchain_rtvs, swapchain_rtvs, sizeof(G.swapchain_rtvs));
IDXGIFactory6_Release(factory);
scratch_end(scratch);
}
/* ========================== *
* Dx12 shader initialization
* ========================== */
INTERNAL void dx12_init_shaders(void)
{
}
/* ========================== *
* Handle
* ========================== */
INTERNAL void dx12_texture_release(struct dx12_texture *t);
INTERNAL struct gpu_handle handle_alloc(enum dx12_handle_kind kind, void *data)
{
u64 old_gen = 0;
u64 idx = 0;
struct dx12_handle_entry *entry = NULL;
{
struct sys_lock lock = sys_mutex_lock_e(&G.handle_entries_mutex);
if (G.first_free_handle_entry) {
entry = G.first_free_handle_entry;
G.first_free_handle_entry = entry->next_free;
old_gen = entry->gen;
idx = entry->idx;
} else {
entry = arena_push_no_zero(&G.handle_entries_arena, struct dx12_handle_entry);
idx = G.num_handle_entries_reserved++;
}
sys_mutex_unlock(&lock);
}
MEMZERO_STRUCT(entry);
entry->kind = kind;
entry->gen = old_gen + 1;
entry->idx = idx;
entry->data = data;
struct gpu_handle res = ZI;
res.gen = entry->gen;
res.idx = entry->idx;
return res;
}
INTERNAL struct dx12_handle_entry *handle_get_entry(struct gpu_handle handle, struct sys_lock *lock)
{
sys_assert_locked_e_or_s(lock, &G.handle_entries_mutex);
struct dx12_handle_entry *res = NULL;
if (handle.idx > 0 && handle.idx < G.num_handle_entries_reserved) {
struct dx12_handle_entry *tmp = &((struct dx12_handle_entry *)G.handle_entries_arena.base)[handle.idx];
if (tmp->gen == handle.gen) {
res = tmp;
}
}
return res;
}
/* TODO: The GPU api should ensure that resources freed by the caller will not cause issues on the GPU (via fencing),
* however the caller is responsible for managing resource lifetimes on the CPU side (e.g. using sprites w/ sprite scopes
* to ensure freed textures aren't being used in pending command lists. */
void gpu_release(struct gpu_handle handle)
{
enum dx12_handle_kind kind = NULL;
void *data = NULL;
/* Release handle entry */
struct sys_lock lock = sys_mutex_lock_e(&G.handle_entries_mutex);
{
struct dx12_handle_entry *entry = handle_get_entry(handle, &lock);
if (entry) {
kind = entry->kind;
data = entry->data;
}
++entry->gen;
entry->next_free = G.first_free_handle_entry;
G.first_free_handle_entry = entry;
}
sys_mutex_unlock(&lock);
/* Release data */
if (data) {
switch (kind) {
default: break;
case DX12_HANDLE_KIND_TEXTURE:
{
dx12_texture_release(data);
} break;
}
}
}
/* ========================== *
* Texture
* ========================== */
INTERNAL void dx12_texture_release(struct dx12_texture *t)
{
(UNUSED)t;
}
struct gpu_handle gpu_texture_alloc(enum gpu_texture_format format, u32 flags, struct v2i32 size, void *initial_data)
{
(UNUSED)format;
(UNUSED)flags;
(UNUSED)size;
(UNUSED)initial_data;
struct dx12_texture *t = NULL;
return handle_alloc(DX12_HANDLE_KIND_TEXTURE, t);
}
void gpu_texture_clear(struct gpu_handle target_texture, u32 clear_color)
{
(UNUSED)target_texture;
(UNUSED)clear_color;
}
struct v2i32 gpu_texture_get_size(struct gpu_handle texture)
{
(UNUSED)texture;
struct v2i32 res = ZI;
return res;
}
/* ========================== *
* Cmd buffer
* ========================== */
struct gpu_handle gpu_cmd_store_alloc(void)
{
struct gpu_handle res = ZI;
return res;
}
void gpu_push_cmd(struct gpu_handle gpu_cmd_store, struct gpu_cmd_params params)
{
(UNUSED)gpu_cmd_store;
(UNUSED)params;
}
void gpu_submit_cmds(struct gpu_handle gpu_cmd_store)
{
(UNUSED)gpu_cmd_store;
}
/* ========================== *
* Dispatch
* ========================== */
struct gpu_handle gpu_dispatch_state_alloc(void)
{
struct gpu_handle res = ZI;
return res;
}
void gpu_dispatch(struct gpu_handle gpu_dispatch_state, struct gpu_dispatch_params params)
{
(UNUSED)gpu_dispatch_state;
(UNUSED)params;
}
/* ========================== *
* Backbuffer
* ========================== */
struct gpu_handle gpu_recreate_backbuffer(struct v2i32 size)
{
(UNUSED)size;
struct gpu_handle res = ZI;
return res;
}
void gpu_swap_backbuffer(i32 vsync)
{
(UNUSED)vsync;
}
#endif