diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index ae1fb713e..e99b63e43 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -24,6 +24,11 @@ set(SOURCES "Source/Utils.h" "Source/Utils.${GRAPHICS_API}.${BABYLON_NATIVE_PLATFORM_IMPL_EXT}") +if(GRAPHICS_API STREQUAL "D3D11" OR GRAPHICS_API STREQUAL "D3D12") + set(SOURCES ${SOURCES} + "Source/Tests.Device.cpp") +endif() + if(GRAPHICS_API STREQUAL "D3D11") set(SOURCES ${SOURCES} "Source/Tests.Device.${GRAPHICS_API}.cpp" diff --git a/Apps/UnitTests/Source/Tests.Device.cpp b/Apps/UnitTests/Source/Tests.Device.cpp new file mode 100644 index 000000000..ab24ef4af --- /dev/null +++ b/Apps/UnitTests/Source/Tests.Device.cpp @@ -0,0 +1,52 @@ +#include + +#include + +#include "Utils.h" + +extern Babylon::Graphics::Configuration g_deviceConfig; + +// Verifies UpdateDevice replaces the active graphics device after a DisableRendering / EnableRendering cycle. +TEST(Device, UpdateDevice) +{ + Babylon::Graphics::DeviceT deviceA = CreateTestGraphicsDevice(); + ASSERT_NE(deviceA, nullptr); + + Babylon::Graphics::DeviceT deviceB = nullptr; + + { + // Inherit Window / Width / Height from the App layer's config so bgfx can manage its own + // swap chain on the HWND. We deliberately do NOT set BackBufferColor here -- the + // caller-provided-BackBuffer flow is a separate concern covered by TEST(Device, BackBuffer). + Babylon::Graphics::Configuration config = g_deviceConfig; + config.Device = deviceA; + + Babylon::Graphics::Device device{config}; + + // Drive a frame to force EnableRendering -> bgfx::init with deviceA. + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + + EXPECT_EQ(device.GetPlatformInfo().Device, deviceA); + + deviceB = CreateTestGraphicsDevice(); + ASSERT_NE(deviceB, nullptr); + + // Tear bgfx down before pointing the device at the new graphics device. + device.DisableRendering(); + + device.UpdateDevice(deviceB); + + // Drive another frame to force EnableRendering -> bgfx::init with deviceB. + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + + EXPECT_EQ(device.GetPlatformInfo().Device, deviceB); + // Note: no EXPECT_NE(deviceB, deviceA). On D3D12 with WARP, D3D12CreateDevice returns the + // same singleton pointer on successive calls so distinctness is not assertable. On D3D11 + // distinctness holds but exercising it does not add value over the EXPECT_EQ above. + } // Babylon::Graphics::Device destructs here, calling DisableRendering on deviceB. + + DestroyTestGraphicsDevice(deviceB); + DestroyTestGraphicsDevice(deviceA); +} diff --git a/Apps/UnitTests/Source/Utils.D3D11.cpp b/Apps/UnitTests/Source/Utils.D3D11.cpp index e5bfd2c26..8e57368c8 100644 --- a/Apps/UnitTests/Source/Utils.D3D11.cpp +++ b/Apps/UnitTests/Source/Utils.D3D11.cpp @@ -26,3 +26,28 @@ void DestroyTestTexture(Babylon::Graphics::TextureT texture) { texture->Release(); } + +Babylon::Graphics::DeviceT CreateTestGraphicsDevice() +{ + ID3D11Device* device = nullptr; + EXPECT_HRESULT_SUCCEEDED(D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_WARP, + nullptr, + 0, + nullptr, + 0, + D3D11_SDK_VERSION, + &device, + nullptr, + nullptr)); + return device; +} + +void DestroyTestGraphicsDevice(Babylon::Graphics::DeviceT device) +{ + if (device != nullptr) + { + device->Release(); + } +} diff --git a/Apps/UnitTests/Source/Utils.D3D12.cpp b/Apps/UnitTests/Source/Utils.D3D12.cpp index 65b4f6d23..06197ad60 100644 --- a/Apps/UnitTests/Source/Utils.D3D12.cpp +++ b/Apps/UnitTests/Source/Utils.D3D12.cpp @@ -1,6 +1,9 @@ #include #include "Utils.h" +#include +#include + Babylon::Graphics::TextureT CreateTestTexture(Babylon::Graphics::DeviceT device, uint32_t width, uint32_t height, uint32_t arraySize) { D3D12_RESOURCE_DESC desc{}; @@ -39,3 +42,25 @@ void DestroyTestTexture(Babylon::Graphics::TextureT texture) { texture->Release(); } + +Babylon::Graphics::DeviceT CreateTestGraphicsDevice() +{ + winrt::com_ptr factory; + winrt::check_hresult(CreateDXGIFactory1(IID_PPV_ARGS(factory.put()))); + + winrt::com_ptr warpAdapter; + winrt::check_hresult(factory->EnumWarpAdapter(IID_PPV_ARGS(warpAdapter.put()))); + + ID3D12Device* device = nullptr; + winrt::check_hresult(D3D12CreateDevice(warpAdapter.get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device))); + + return device; +} + +void DestroyTestGraphicsDevice(Babylon::Graphics::DeviceT device) +{ + if (device != nullptr) + { + device->Release(); + } +} diff --git a/Apps/UnitTests/Source/Utils.h b/Apps/UnitTests/Source/Utils.h index a3ee6a982..60e7a76fd 100644 --- a/Apps/UnitTests/Source/Utils.h +++ b/Apps/UnitTests/Source/Utils.h @@ -4,3 +4,17 @@ Babylon::Graphics::TextureT CreateTestTexture(Babylon::Graphics::DeviceT device, uint32_t width, uint32_t height, uint32_t arraySize = 1); void DestroyTestTexture(Babylon::Graphics::TextureT texture); + +// Returns a graphics device suitable for use as Babylon::Graphics::Configuration::Device. The +// returned handle is owned by the caller and must be released with DestroyTestGraphicsDevice. +// +// Defined only on D3D11 and D3D12 -- the Tests.Device.cpp test that consumes these helpers is +// gated to those backends. On D3D11 returns an ID3D11Device created via D3D11CreateDevice(WARP); +// on D3D12 returns an ID3D12Device created via D3D12CreateDevice on the WARP DXGI adapter. +// +// Note: on D3D12, D3D12CreateDevice(WARP) returns the same singleton pointer on successive calls +// in the same process, so two calls in a row may return equal handles. D3D11CreateDevice(WARP) +// does return distinct handles per call. +Babylon::Graphics::DeviceT CreateTestGraphicsDevice(); + +void DestroyTestGraphicsDevice(Babylon::Graphics::DeviceT device);