mcap/src/graphics/DX11.jai

692 lines
26 KiB
Plaintext

// Calls D3D11 Api for loading and displaying textures.
image_show_texture :: (using id: *Image_Data) {
show_d3d_texture(*d3d_texture, scale_factor);
}
icon_load_texture :: (using id: *Image_Data) {
if !g_pd3dDevice then return;
desc : D3D11_TEXTURE2D_DESC;
memset(*desc, 0, size_of(D3D11_TEXTURE2D_DESC));
desc.Width = cast(u32)width;
desc.Height = cast(u32)height;
desc.MipLevels = 1;
desc.ArraySize = 1;
// desc.Format = DXGI_FORMAT.R8G8B8A8_UNORM;
desc.Format = DXGI_FORMAT.B8G8R8A8_UNORM; // BGRA, as GDI hands out bitmaps
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE.DEFAULT;
desc.BindFlags = D3D11_BIND_FLAG.SHADER_RESOURCE;
desc.CPUAccessFlags = 0;
// d3d_texture.texture = null;
subResource : D3D11_SUBRESOURCE_DATA;
memset(*subResource, 0, size_of(D3D11_SUBRESOURCE_DATA));
subResource.pSysMem = id.data;
subResource.SysMemPitch = (desc.Width * 4);
subResource.SysMemSlicePitch = 0;
hr := ID3D11Device_CreateTexture2D(g_pd3dDevice, *desc, *subResource, *d3d_texture.texture);
if (hr != S_OK) {
print("ID3D11Device_CreateBuffer failed: %\n", hr);
return;
}
srvDesc : D3D11_SHADER_RESOURCE_VIEW_DESC;
memset(*srvDesc, 0, size_of(D3D11_SHADER_RESOURCE_VIEW_DESC));
srvDesc.Format = desc.Format;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION.TEXTURE2D;
srvDesc.Texture2D.MipLevels = desc.MipLevels;
srvDesc.Texture2D.MostDetailedMip = 0;
hr = ID3D11Device_CreateShaderResourceView(g_pd3dDevice, d3d_texture.texture, *srvDesc, *d3d_texture.out_srv);
if (hr != S_OK) {
print("ID3D11Device_CreateShaderResourceView failed: %\n", hr);
IUnknown_Release(d3d_texture.texture);
d3d_texture.texture = null;
return;
}
IUnknown_Release(d3d_texture.texture);
d3d_texture.texture = null;
d3d_texture.out_width = width;
d3d_texture.out_height = height;
d3d_texture.loaded = true;
}
show_d3d_texture :: (using d3d_texture: *D3D11_Texture, scale_factor: float32 = 1) {
if loaded {
// ImGui.Image(Get_ImTextureRef(d3d_texture),
// Vector2.{scale_factor * cast(float32)d3d_texture.out_width, scale_factor * cast(float32)d3d_texture.out_height}, Vector2.{0, 0},
// Vector2.{1,1}, Vector4.{1,1,1,1}, Vector4.{0,0,0,0});
}
}
show_d3d_texture_height :: (using d3d_texture: *D3D11_Texture, new_height: float32) {
new_scale_factor := new_height / d3d_texture.out_height;
new_width := d3d_texture.out_width * new_scale_factor;
if loaded {
ImGui.Image(Get_ImTextureRef(d3d_texture),
Vector2.{new_width, new_height}, Vector2.{0, 0},
Vector2.{1,1}, Vector4.{1,1,1,1}, Vector4.{0,0,0,0});
}
}
show_d3d_texture_width :: (using d3d_texture: *D3D11_Texture, new_width: float32) {
new_scale_factor := new_width / d3d_texture.out_width;
new_height := d3d_texture.out_height * new_scale_factor;
if loaded {
ImGui.Image(Get_ImTextureRef(d3d_texture),
Vector2.{new_width, new_height}, Vector2.{0, 0},
Vector2.{1,1}, Vector4.{1,1,1,1}, Vector4.{0,0,0,0});
}
}
DXGI_Handle_Vsync :: () {
sync_interval: u32 = xx ifx app.vsync_on && app.frame_count > 1 then 1 else 0;
hr := IDXGISwapChain_Present(g_pSwapChain, sync_interval, 0);
g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
d3d11_check_for_device_loss(hr, "IDXGISwapChain_Present");
}
D3D11_HandleWindowResize :: () {
if g_device_lost then return;
if (g_resizeWidth != 0 && g_resizeHeight != 0) {
D3D11_CleanupRenderTarget();
hr := IDXGISwapChain_ResizeBuffers(g_pSwapChain, 0, g_resizeWidth, g_resizeHeight, DXGI_FORMAT.UNKNOWN, 0);
g_resizeWidth = 0;
g_resizeHeight = 0;
if d3d11_check_for_device_loss(hr, "IDXGISwapChain_ResizeBuffers") then return;
if !D3D11_CreateRenderTarget() {
log_error("[D3D11] No render target after a resize; recreating the device.");
g_device_lost = true;
}
}
}
// Returns true when hr failed because the device is gone. Recovery then runs from
// d3d11_recover_lost_device_if_needed, and no frame is drawn until it succeeds.
d3d11_check_for_device_loss :: (hr: HRESULT, call: string) -> bool {
if !FAILED(hr) then return false;
reason := ID3D11Device_GetDeviceRemovedReason(g_pd3dDevice);
if reason == S_OK {
// A Present that keeps failing would log every frame for as long as the app runs.
d3d11_failures_unlogged += 1;
now := current_time_monotonic();
if !d3d11_failure_logged || to_float64_seconds(now - d3d11_failure_logged_at) >= 10 {
if d3d11_failures_unlogged > 1 log_error("[D3D11] % failed: % (% failures since the last report)", call, d3d11_hresult_string(hr), d3d11_failures_unlogged);
else log_error("[D3D11] % failed: %", call, d3d11_hresult_string(hr));
d3d11_failure_logged = true;
d3d11_failure_logged_at = now;
d3d11_failures_unlogged = 0;
}
return false;
}
if !g_device_lost {
log_error("[D3D11] % failed with %; the device was removed (reason %). Recreating it.",
call, d3d11_hresult_string(hr), d3d11_hresult_string(reason));
}
g_device_lost = true;
return true;
}
// Present is the only call that reports a removed device, but the ImGui backend creates its device
// objects in NewFrame and textures in RenderDrawData, and asserts when that fails. So the device is
// asked before each; a removal in the moment between the check and the call can still assert.
d3d11_device_was_removed :: () -> bool {
if g_device_lost then return true;
reason := ID3D11Device_GetDeviceRemovedReason(g_pd3dDevice);
if reason == S_OK then return false;
log_error("[D3D11] The device was removed (reason %). Recreating it.", d3d11_hresult_string(reason));
g_device_lost = true;
return true;
}
D3D11_CleanupDevice :: () {
D3D11_CleanupRenderTarget();
D3D11_ReleaseSwapChain();
if (g_pd3dDeviceContext) { IUnknown_Release(g_pd3dDeviceContext); g_pd3dDeviceContext = null; }
if (g_pd3dDevice) { IUnknown_Release(g_pd3dDevice); g_pd3dDevice = null; }
}
D3D11_CleanupRenderTarget :: () {
if (g_mainRenderTargetView) { IUnknown_Release(g_mainRenderTargetView); g_mainRenderTargetView = null; }
}
D3D11_ReleaseSwapChain :: () {
if (g_pSwapChain) { IUnknown_Release(g_pSwapChain); g_pSwapChain = null; }
}
D3D11_InitDevice :: (hwnd: HWND, requested_driver_type: D3D_DRIVER_TYPE) -> bool {
driver := requested_driver_type;
device_start := current_time_monotonic();
device, device_context, level, hr := d3d11_create_device(driver);
if FAILED(hr) {
fallback := ifx driver == .HARDWARE then D3D_DRIVER_TYPE.WARP else .HARDWARE;
log_error("[D3D11] D3D11CreateDevice(%) failed with %; trying % instead.", driver, d3d11_hresult_string(hr), fallback);
driver = fallback;
device, device_context, level, hr = d3d11_create_device(driver);
if FAILED(hr) {
log_error("[D3D11] D3D11CreateDevice(%) failed with %.", driver, d3d11_hresult_string(hr));
return false;
}
}
perf.startup_device_ms = to_float64_seconds(current_time_monotonic() - device_start) * 1000.0;
swap_chain_start := current_time_monotonic();
if !d3d11_attach_device(device, device_context, hwnd) {
d3d11_release_device(device, device_context);
return false;
}
perf.startup_swap_chain_ms = to_float64_seconds(current_time_monotonic() - swap_chain_start) * 1000.0;
g_driver_type = driver;
g_feature_level = level;
d3d11_update_adapter_name();
return true;
}
D3D11_CreateRenderTarget :: () -> bool {
pBackBuffer: *ID3D11Texture2D;
hr := IDXGISwapChain_GetBuffer(g_pSwapChain, 0, *uid(ID3D11Texture2D_UUID), xx *pBackBuffer);
if FAILED(hr) {
log_error("[D3D11] IDXGISwapChain_GetBuffer failed: %", d3d11_hresult_string(hr));
return false;
}
defer IUnknown_Release(pBackBuffer);
hr = ID3D11Device_CreateRenderTargetView(g_pd3dDevice, pBackBuffer, null, *g_mainRenderTargetView);
if FAILED(hr) {
log_error("[D3D11] ID3D11Device_CreateRenderTargetView failed: %", d3d11_hresult_string(hr));
return false;
}
return true;
}
delete_d3d_texture :: (using d3d_texture: *D3D11_Texture) {
if loaded {
IUnknown_Release(out_srv);
loaded = false;
}
}
Image_Data :: struct {
data: *u8;
width: s32;
height: s32;
depth: s32;
scale_factor: float32 = 1.0;
d3d_texture: D3D11_Texture;
}
// texture_of :: (using im: *Image_Data) -> ImGui.ImTextureID #expand {
// Get_ImTextureRef(*d3d_texture);
// }
D3D11_Texture :: struct {
texture: *ID3D11Texture2D;
out_srv: *ID3D11ShaderResourceView;
out_width : s32;
out_height : s32;
loaded := false;
}
Get_ImTextureRef :: (using d3d_texture: *D3D11_Texture) -> ImGui.ImTextureRef #expand {
ref: ImGui.ImTextureRef = { null, cast(ImGui.ImTextureID)out_srv };
return ref;
}
// A texture from tightly packed RGBA pixels, at most GPU_TEXTURE_MAX_SIDE a side. Its handle is the
// shader resource view, which is what the ImGui DX11 backend draws. A zero handle when it failed.
gpu_texture_create :: (pixels: *u8, width: s32, height: s32) -> Gpu_Texture {
result: Gpu_Texture;
if !g_pd3dDevice || width <= 0 || height <= 0 || width > GPU_TEXTURE_MAX_SIDE || height > GPU_TEXTURE_MAX_SIDE return result;
desc: D3D11_TEXTURE2D_DESC;
desc.Width = cast(u32) width;
desc.Height = cast(u32) height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = .R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = .IMMUTABLE;
desc.BindFlags = .SHADER_RESOURCE;
data: D3D11_SUBRESOURCE_DATA;
data.pSysMem = pixels;
data.SysMemPitch = cast(u32) width * 4;
texture: *ID3D11Texture2D;
hr := ID3D11Device_CreateTexture2D(g_pd3dDevice, *desc, *data, *texture);
if FAILED(hr) {
log_error("[D3D11] CreateTexture2D (%x%) failed: %", width, height, d3d11_hresult_string(hr));
return result;
}
defer IUnknown_Release(texture); // the view keeps it alive
view_desc: D3D11_SHADER_RESOURCE_VIEW_DESC;
view_desc.Format = desc.Format;
view_desc.ViewDimension = .TEXTURE2D;
view_desc.Texture2D.MipLevels = 1;
view: *ID3D11ShaderResourceView;
hr = ID3D11Device_CreateShaderResourceView(g_pd3dDevice, texture, *view_desc, *view);
if FAILED(hr) {
log_error("[D3D11] CreateShaderResourceView failed: %", d3d11_hresult_string(hr));
return result;
}
result.handle = view;
result.width = width;
result.height = height;
return result;
}
gpu_texture_release :: (texture: *Gpu_Texture) {
if texture.handle IUnknown_Release(cast(*ID3D11ShaderResourceView) texture.handle);
texture.* = .{};
}
// ---------------------------------------------------------------------------------------------
// #WarpFirstStartup
//
// Creating a hardware D3D11 device loads the vendor's user-mode driver, which can take hundreds of
// milliseconds. A WARP (software) device can be initialized rapidly and set to work rendering
// while the hardware device initializes in the background. So the window starts on WARP while a
// worker thread creates the hardware device, and the renderer moves across at a frame boundary
// once it is ready.
//
// main(): D3D11_InitDevice(hwnd, .WARP) first frames render on WARP
// d3d11_start_hardware_device_thread() once WARP is up, so the two creations never overlap
// main loop: d3d11_swap_to_hardware_if_ready() between the message pump and ImGui.NewFrame()
// exit: d3d11_finish_hardware_device_thread()
// ---------------------------------------------------------------------------------------------
d3d11_hw: D3D11_Hardware_Device_Task;
D3D11_Hardware_Device_Task :: struct {
thread: *Thread;
swap_attempted: bool; // set once, whether the swap succeeded or not; we never retry
// Written by the worker, read by the main thread only after thread_is_done() has returned true.
device: *ID3D11Device;
device_context: *ID3D11DeviceContext;
level: D3D_FEATURE_LEVEL;
hr: HRESULT = E_FAIL;
create_ms: float64; // how long D3D11CreateDevice took on the worker
finish_time: Apollo_Time; // when the worker finished
}
d3d11_start_hardware_device_thread :: () {
push_allocator(context.default_allocator);
d3d11_hw.thread = New(Thread);
if !thread_init(d3d11_hw.thread, d3d11_hardware_device_thread_proc, name = "d3d11 device") {
log_error("[D3D11] Could not start the hardware device thread; staying on WARP.");
free(d3d11_hw.thread);
d3d11_hw.thread = null;
return;
}
thread_start(d3d11_hw.thread);
}
d3d11_finish_hardware_device_thread :: () {
if !d3d11_hw.thread then return;
thread_is_done(d3d11_hw.thread, -1);
d3d11_free_hardware_device_thread();
d3d11_release_device(d3d11_hw.device, d3d11_hw.device_context);
d3d11_hw.device = null;
d3d11_hw.device_context = null;
}
// Must run between the message pump and ImGui.NewFrame(): the swap destroys and recreates every
// ImGui platform window, which is only legal outside a frame.
d3d11_swap_to_hardware_if_ready :: () {
if d3d11_hw.swap_attempted || !d3d11_hw.thread then return;
if !thread_is_done(d3d11_hw.thread) then return;
d3d11_hw.swap_attempted = true;
d3d11_free_hardware_device_thread();
new_device := d3d11_hw.device;
new_context := d3d11_hw.device_context;
d3d11_hw.device = null;
d3d11_hw.device_context = null;
if FAILED(d3d11_hw.hr) || !new_device || !new_context {
log_error("[D3D11] Hardware device creation failed with %; staying on WARP.", d3d11_hresult_string(d3d11_hw.hr));
d3d11_release_device(new_device, new_context);
return;
}
perf.hw_device_create_ms = d3d11_hw.create_ms;
perf.hw_device_ready_ms = to_float64_seconds(d3d11_hw.finish_time - app.startup_time) * 1000.0;
// Nothing to gain: WARP was unavailable and D3D11_InitDevice already fell back to hardware.
if g_driver_type == .HARDWARE || !d3d11_is_worth_swapping_to(new_device) {
d3d11_release_device(new_device, new_context);
return;
}
swap_start := current_time_monotonic();
if !d3d11_replace_device(new_device, new_context, .HARDWARE, d3d11_hw.level) {
log_error("[D3D11] Could not bring up a swap chain on the hardware device; staying on WARP.");
if !g_pSwapChain {
log_error("[D3D11] Lost the renderer entirely while swapping devices. Exiting.");
app.should_exit = true;
}
return;
}
perf.hw_swap_ms = to_float64_seconds(current_time_monotonic() - swap_start) * 1000.0;
log("[D3D11] Swapped to '%'. Device creation took % ms on the worker, ready % ms after launch; the swap took % ms.",
perf.adapter_name, FF(perf.hw_device_create_ms, 1), FF(perf.hw_device_ready_ms, 1), FF(perf.hw_swap_ms, 1));
}
// Must run between the message pump and ImGui.NewFrame(), for the same reason as
// d3d11_swap_to_hardware_if_ready. Retries at most once a second until a device comes up.
d3d11_recover_lost_device_if_needed :: () {
if !g_device_lost then return;
if to_float64_seconds(current_time_monotonic() - g_device_recovery_last_attempt) < 1.0 then return;
g_device_recovery_last_attempt = current_time_monotonic();
// A pending hardware device may have been created on the adapter that was just removed.
if d3d11_hw.thread && !d3d11_hw.swap_attempted {
d3d11_hw.swap_attempted = true;
d3d11_finish_hardware_device_thread();
}
driver := D3D_DRIVER_TYPE.HARDWARE;
device, device_context, level, hr := d3d11_create_device(driver);
if FAILED(hr) {
log_error("[D3D11] Recreating a hardware device failed with %; trying WARP.", d3d11_hresult_string(hr));
driver = .WARP;
device, device_context, level, hr = d3d11_create_device(driver);
if FAILED(hr) {
log_error("[D3D11] Recreating a WARP device failed with %; retrying in a second.", d3d11_hresult_string(hr));
return;
}
}
if !d3d11_replace_device(device, device_context, driver, level) {
log_error("[D3D11] Could not bring up a swap chain on the recreated device; retrying in a second.");
return;
}
g_device_lost = false;
log("[D3D11] Recovered from device loss on '%' (%).", perf.adapter_name, g_driver_type);
}
// Moves the renderer, the swap chain, and both ImGui backends onto new_device, which it takes
// ownership of. On failure new_device is released and the old device is put back, though its
// swap chain can still fail to come back: check g_pSwapChain.
d3d11_replace_device :: (new_device: *ID3D11Device, new_context: *ID3D11DeviceContext,
driver: D3D_DRIVER_TYPE, level: D3D_FEATURE_LEVEL) -> bool {
// ImplDX11_Shutdown destroys every platform window, including the main viewport's Win32 data,
// which only ImplWin32_Init restores; so both backends go down and come back up together.
//
// ImplWin32_Shutdown calls ClearPlatformHandlers, which also drops the clipboard, open-in-shell
// and IME handlers that ImGui installed at context creation and that ImplWin32_Init never
// reinstalls. Without copying and restoring them, InputText copy/paste silently breaks for the rest of the run.
platform_io := ImGui.GetPlatformIO();
saved_get_clipboard_text := platform_io.Platform_GetClipboardTextFn;
saved_set_clipboard_text := platform_io.Platform_SetClipboardTextFn;
saved_clipboard_user_data := platform_io.Platform_ClipboardUserData;
saved_open_in_shell := platform_io.Platform_OpenInShellFn;
saved_open_in_shell_user_data := platform_io.Platform_OpenInShellUserData;
saved_set_ime_data := platform_io.Platform_SetImeDataFn;
saved_ime_user_data := platform_io.Platform_ImeUserData;
ImGui.ImplDX11_Shutdown();
ImGui.ImplWin32_Shutdown();
old_device := g_pd3dDevice;
old_context := g_pd3dDeviceContext;
// The window can only have one swap chain, so the old one has to be gone before the new one is
// created. Flush after ClearState so the deferred destruction actually happens now.
D3D11_CleanupRenderTarget();
ID3D11DeviceContext_ClearState(old_context);
ID3D11DeviceContext_Flush(old_context);
D3D11_ReleaseSwapChain();
replaced := d3d11_attach_device(new_device, new_context, platform.hwnd);
if replaced {
g_driver_type = driver;
g_feature_level = level;
d3d11_release_device(old_device, old_context);
} else {
d3d11_release_device(new_device, new_context);
d3d11_attach_device(old_device, old_context, platform.hwnd);
}
// The new chain was sized to the current client area, so any resize queued before the swap is moot.
g_resizeWidth = 0;
g_resizeHeight = 0;
g_SwapChainOccluded = false;
ImGui.ImplWin32_Init(platform.hwnd);
ImGui.ImplDX11_Init(
cast,no_check(*ImGui.ID3D11Device) g_pd3dDevice,
cast,no_check(*ImGui.ID3D11DeviceContext) g_pd3dDeviceContext);
imgui_reset_key_down_state();
platform_io.Platform_GetClipboardTextFn = saved_get_clipboard_text;
platform_io.Platform_SetClipboardTextFn = saved_set_clipboard_text;
platform_io.Platform_ClipboardUserData = saved_clipboard_user_data;
platform_io.Platform_OpenInShellFn = saved_open_in_shell;
platform_io.Platform_OpenInShellUserData = saved_open_in_shell_user_data;
if !platform_io.Platform_SetImeDataFn {
platform_io.Platform_SetImeDataFn = saved_set_ime_data;
platform_io.Platform_ImeUserData = saved_ime_user_data;
}
d3d11_update_adapter_name();
// Textures belong to the device they were made on: release any the app made with
// icon_load_texture here, and make them again on the new device. Textures made with
// gpu_texture_create are made again by whoever holds them when they see this change.
if replaced g_texture_generation += 1;
return replaced;
}
#scope_file
d3d11_hardware_device_thread_proc :: (thread: *Thread) -> s64 {
start := current_time_monotonic();
device, device_context, level, hr := d3d11_create_device(.HARDWARE);
finish := current_time_monotonic();
d3d11_hw.device = device;
d3d11_hw.device_context = device_context;
d3d11_hw.level = level;
d3d11_hw.hr = hr;
d3d11_hw.create_ms = to_float64_seconds(finish - start) * 1000.0;
d3d11_hw.finish_time = finish;
return 0;
}
d3d11_free_hardware_device_thread :: () {
push_allocator(context.default_allocator);
thread_deinit(d3d11_hw.thread);
free(d3d11_hw.thread);
d3d11_hw.thread = null;
}
// Swapping WARP for something that is not actually a GPU buys nothing and costs a visible hitch.
d3d11_is_worth_swapping_to :: (device: *ID3D11Device) -> bool {
removed := ID3D11Device_GetDeviceRemovedReason(device);
if removed != S_OK {
log_error("[D3D11] The hardware device was removed before we could swap to it: %", d3d11_hresult_string(removed));
return false;
}
desc, ok := d3d11_adapter_desc(device);
if ok && desc.VendorId == DXGI_VENDOR_ID_MICROSOFT && desc.DeviceId == DXGI_DEVICE_ID_BASIC_RENDER_DRIVER {
log("[D3D11] The hardware adapter is the Microsoft Basic Render Driver; staying on WARP.");
return false;
}
return true;
}
d3d11_create_device :: (driver_type: D3D_DRIVER_TYPE) ->
(device: *ID3D11Device, device_context: *ID3D11DeviceContext, level: D3D_FEATURE_LEVEL, hr: HRESULT) {
flags: D3D11_CREATE_DEVICE_FLAG;
levels: [2]D3D_FEATURE_LEVEL = .[._11_0, ._10_0];
device: *ID3D11Device;
device_context: *ID3D11DeviceContext;
level: D3D_FEATURE_LEVEL;
hr := D3D11CreateDevice(null, driver_type, null, flags,
levels.data, xx levels.count, D3D11_SDK_VERSION, *device, *level, *device_context);
return device, device_context, level, hr;
}
d3d11_attach_device :: (device: *ID3D11Device, device_context: *ID3D11DeviceContext, hwnd: HWND) -> bool {
g_pd3dDevice = device;
g_pd3dDeviceContext = device_context;
swap_chain, ok := d3d11_create_swap_chain(device, hwnd);
if !ok then return false;
g_pSwapChain = swap_chain;
if !D3D11_CreateRenderTarget() {
D3D11_ReleaseSwapChain();
return false;
}
return true;
}
// MakeWindowAssociation is per-factory, so it is redone every time the swap chain is recreated.
d3d11_create_swap_chain :: (device: *ID3D11Device, hwnd: HWND) -> (swap_chain: *IDXGISwapChain, success: bool) {
factory, have_factory := d3d11_factory_for_device(device);
if !have_factory then return null, false;
defer IUnknown_Release(factory);
sd : DXGI_SWAP_CHAIN_DESC;
sd.BufferCount = 2;
sd.BufferDesc.Width = 0; // 0x0 sizes the buffers to the window's client area
sd.BufferDesc.Height = 0;
sd.BufferDesc.Format = .R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.Flags = xx DXGI_SWAP_CHAIN_FLAG.ALLOW_MODE_SWITCH;
sd.BufferUsage = DXGI_USAGE.RENDER_TARGET_OUTPUT;
sd.OutputWindow = hwnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = .TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT.DISCARD;
swap_chain: *IDXGISwapChain;
hr := IDXGIFactory_CreateSwapChain(factory, device, *sd, *swap_chain);
if FAILED(hr) {
log_error("[D3D11] IDXGIFactory_CreateSwapChain failed: %", d3d11_hresult_string(hr));
return null, false;
}
// Disable DXGI's default Alt+Enter fullscreen behavior.
// - You are free to leave this enabled, but it will not work properly with multiple viewports.
// - This must be done for all windows associated to the device. Our DX11 backend does this automatically for secondary viewports that it creates.
IDXGIFactory_MakeWindowAssociation(factory, hwnd, cast(u32) DXGI_MWA.NO_ALT_ENTER);
return swap_chain, true;
}
d3d11_factory_for_device :: (device: *ID3D11Device) -> (factory: *IDXGIFactory, success: bool) {
adapter, have_adapter := d3d11_adapter_for_device(device);
if !have_adapter then return null, false;
defer IUnknown_Release(adapter);
factory: *IDXGIFactory;
hr := IDXGIObject_GetParent(adapter, *uid(IDXGIFactory_UUID), xx *factory);
if FAILED(hr) {
log_error("[D3D11] IDXGIObject_GetParent(IDXGIFactory) failed: %", d3d11_hresult_string(hr));
return null, false;
}
return factory, true;
}
d3d11_adapter_for_device :: (device: *ID3D11Device) -> (adapter: *IDXGIAdapter, success: bool) {
dxgi_device: *IDXGIDevice;
hr := IUnknown_QueryInterface(device, *uid(IDXGIDevice_UUID), xx *dxgi_device);
if FAILED(hr) {
log_error("[D3D11] QueryInterface(IDXGIDevice) failed: %", d3d11_hresult_string(hr));
return null, false;
}
defer IUnknown_Release(dxgi_device);
adapter: *IDXGIAdapter;
hr = IDXGIDevice_GetAdapter(dxgi_device, *adapter);
if FAILED(hr) {
log_error("[D3D11] IDXGIDevice_GetAdapter failed: %", d3d11_hresult_string(hr));
return null, false;
}
return adapter, true;
}
d3d11_adapter_desc :: (device: *ID3D11Device) -> (desc: DXGI_ADAPTER_DESC, success: bool) {
desc: DXGI_ADAPTER_DESC;
adapter, have_adapter := d3d11_adapter_for_device(device);
if !have_adapter then return desc, false;
defer IUnknown_Release(adapter);
if FAILED(IDXGIAdapter_GetDesc(adapter, *desc)) then return desc, false;
return desc, true;
}
d3d11_release_device :: (device: *ID3D11Device, device_context: *ID3D11DeviceContext) {
if device_context then IUnknown_Release(device_context);
if device then IUnknown_Release(device);
}
d3d11_update_adapter_name :: () {
push_allocator(context.default_allocator);
if perf.adapter_name.count > 0 then free(perf.adapter_name);
perf.adapter_name = "";
desc, ok := d3d11_adapter_desc(g_pd3dDevice);
if ok {
name, converted := wide_to_utf8(cast(*u16) desc.Description.data);
if converted then perf.adapter_name = name;
}
perf.renderer_is_software = (g_driver_type != .HARDWARE);
}
d3d11_hresult_string :: (hr: HRESULT) -> string {
return tprint("0x%", FormatInt.{value = cast,no_check(u32) hr, base = 16, minimum_digits = 8});
}
DXGI_VENDOR_ID_MICROSOFT :: 0x1414;
DXGI_DEVICE_ID_BASIC_RENDER_DRIVER :: 0x8c;
#scope_export
// ~ Data
g_driver_type: D3D_DRIVER_TYPE = .UNKNOWN;
g_feature_level: D3D_FEATURE_LEVEL = ._11_0;
g_pd3dDevice: *ID3D11Device;
g_pd3dDeviceContext: *ID3D11DeviceContext;
g_pSwapChain: *IDXGISwapChain;
g_mainRenderTargetView: *ID3D11RenderTargetView;
g_resizeWidth: u32 = 0;
g_resizeHeight: u32 = 0;
g_SwapChainOccluded: bool = false;
g_device_lost: bool = false;
g_device_recovery_last_attempt: Apollo_Time;
// d3d11_check_for_device_loss: failures that leave the device in place, reported at most every 10 s.
d3d11_failure_logged: bool;
d3d11_failure_logged_at: Apollo_Time;
d3d11_failures_unlogged: s64;
DXGI_PRESENT_TEST :: 0x00000001;