692 lines
26 KiB
Plaintext
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;
|