diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index 3475fffe73..09aa492e76 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -29,9 +29,11 @@ set_source_files_properties(${TEST_SCRIPTS} PROPERTIES HEADER_FILE_ONLY TRUE) set(SOURCES "Source/App.h" "Source/App.cpp" + "Source/Tests.BgfxCallback.cpp" "Source/Tests.Canvas.TextMetrics.cpp" "Source/Tests.Canvas.Readback.cpp" "Source/Tests.Device.FrameEncoder.cpp" + "Source/Tests.Device.SwapChain.cpp" "Source/Tests.ExternalTexture.cpp" "Source/Tests.ExternalTexture.Lifecycle.cpp" "Source/Tests.ExternalTexture.DeviceLoss.cpp" @@ -55,7 +57,8 @@ endif() if(GRAPHICS_API STREQUAL "D3D11") set(SOURCES ${SOURCES} - "Source/Tests.Device.${GRAPHICS_API}.cpp") + "Source/Tests.Device.${GRAPHICS_API}.cpp" + "Source/Tests.Device.D3D11.ExternalBackBuffer.cpp") endif() if(APPLE) @@ -99,6 +102,11 @@ target_link_libraries(UnitTests target_compile_definitions(UnitTests PRIVATE ${ADDITIONAL_COMPILE_DEFINITIONS}) +if(BABYLON_NATIVE_PLUGIN_SHADERCOMPILER) + target_link_libraries(UnitTests PRIVATE ShaderCompilerInternal) + target_compile_definitions(UnitTests PRIVATE HAS_SHADER_COMPILER) +endif() + # NativeDraco and NativeMeshopt default to OFF, so link and exercise them only when the # consuming build opted in. CI turns both on for the jobs that run UnitTests. if(BABYLON_NATIVE_PLUGIN_NATIVEDRACO) @@ -112,7 +120,7 @@ if(BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT) endif() if(GRAPHICS_API STREQUAL "D3D12") - target_compile_definitions(UnitTests PRIVATE SKIP_RENDER_TESTS) + target_compile_definitions(UnitTests PRIVATE SKIP_RENDER_TESTS BABYLON_NATIVE_GRAPHICS_API_D3D12) endif() if(GRAPHICS_API STREQUAL "Vulkan") diff --git a/Apps/UnitTests/Source/Tests.BgfxCallback.cpp b/Apps/UnitTests/Source/Tests.BgfxCallback.cpp new file mode 100644 index 0000000000..9ea722355b --- /dev/null +++ b/Apps/UnitTests/Source/Tests.BgfxCallback.cpp @@ -0,0 +1,65 @@ +#include + +#include + +#include + +using Babylon::Graphics::BgfxCallback; + +TEST(BgfxCallback, CoalescesScreenshotsAndCapture) +{ + const std::array pixels{3, 2, 1, 255, 6, 5, 4, 255, 0, 0, 0, 0}; + const BgfxCallback::CaptureData data{2, 1, 12, bgfx::TextureFormat::BGRA8, false, pixels.data(), 12}; + const std::vector expected{1, 2, 3, 255, 4, 5, 6, 255}; + + size_t captures{}; + BgfxCallback callback{[&](const auto& captured) { + ++captures; + EXPECT_EQ(captured.Data, pixels.data()); + EXPECT_EQ(captured.Pitch, 12u); + EXPECT_EQ(captured.Format, bgfx::TextureFormat::BGRA8); + }}; + size_t screenshots{}; + for (size_t i = 0; i < 2; ++i) + { + callback.AddScreenShotCallback([&](const auto& captured) { + ++screenshots; + EXPECT_EQ(captured, expected); + }); + } + callback.CaptureNextScreenShot(); + callback.CompleteScreenShot(data); + EXPECT_EQ(captures, 1u); + EXPECT_EQ(screenshots, 2u); + + callback.AddScreenShotCallback([&](const auto&) { ++screenshots; }); + callback.CompleteScreenShot(data); + EXPECT_EQ(captures, 1u); + EXPECT_EQ(screenshots, 3u); +} + +TEST(BgfxCallback, CaptureDoesNotRequireScreenshotCallback) +{ + const std::array pixels{1, 2, 3, 4}; + const BgfxCallback::CaptureData data{1, 1, 4, bgfx::TextureFormat::RGBA8, true, pixels.data(), 4}; + size_t captures{}; + BgfxCallback callback{[&](const auto& captured) { + ++captures; + EXPECT_TRUE(captured.YFlip); + EXPECT_EQ(captured.DataSize, pixels.size()); + }}; + callback.CaptureNextScreenShot(); + callback.CompleteScreenShot(data); + EXPECT_EQ(captures, 1u); +} + +TEST(BgfxCallback, NormalizesFlippedRgbaScreenshots) +{ + const std::array pixels{1, 2, 3, 4, 0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0}; + const BgfxCallback::CaptureData data{1, 2, 8, bgfx::TextureFormat::RGBA8, true, pixels.data(), 16}; + BgfxCallback callback{[](const auto&) {}}; + callback.AddScreenShotCallback([](const auto& captured) { + EXPECT_EQ(captured, (std::vector{5, 6, 7, 8, 1, 2, 3, 4})); + }); + callback.CompleteScreenShot(data); +} diff --git a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp new file mode 100644 index 0000000000..e1a0532b22 --- /dev/null +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp @@ -0,0 +1,447 @@ +#include + +#include +#include +#include +#include +#include + +#include "Helpers.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +extern Babylon::Graphics::Configuration g_deviceConfig; + +namespace +{ + winrt::com_ptr CreateDevice() + { + winrt::com_ptr device{}; + EXPECT_HRESULT_SUCCEEDED(D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_WARP, + nullptr, + 0, + nullptr, + 0, + D3D11_SDK_VERSION, + device.put(), + nullptr, + nullptr)); + return device; + } + + struct RenderTargetTexture + { + winrt::com_ptr Texture; + winrt::com_ptr View; + }; + + struct DepthTexture + { + winrt::com_ptr Texture; + winrt::com_ptr View; + }; + + RenderTargetTexture CreateTestRenderTargetTexture( + ID3D11Device* device, + uint32_t width, + uint32_t height, + uint32_t samples = 1, + DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM) + { + D3D11_TEXTURE2D_DESC desc{}; + desc.Width = width; + desc.Height = height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = format; + desc.SampleDesc.Count = samples; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET; + + winrt::com_ptr texture; + EXPECT_HRESULT_SUCCEEDED(device->CreateTexture2D(&desc, nullptr, texture.put())); + + D3D11_RENDER_TARGET_VIEW_DESC rtvDesc{}; + rtvDesc.Format = desc.Format; + rtvDesc.ViewDimension = samples > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D; + + winrt::com_ptr view; + EXPECT_HRESULT_SUCCEEDED(device->CreateRenderTargetView(texture.get(), &rtvDesc, view.put())); + + return {texture, view}; + } + + RenderTargetTexture CreateArrayMipRenderTarget(ID3D11Device* device) + { + D3D11_TEXTURE2D_DESC desc{}; + desc.Width = 64; + desc.Height = 32; + desc.MipLevels = 3; + desc.ArraySize = 3; + desc.Format = DXGI_FORMAT_R8G8B8A8_TYPELESS; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET; + + winrt::com_ptr texture; + EXPECT_HRESULT_SUCCEEDED(device->CreateTexture2D(&desc, nullptr, texture.put())); + + D3D11_RENDER_TARGET_VIEW_DESC viewDesc{}; + viewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + viewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY; + viewDesc.Texture2DArray.MipSlice = 2; + viewDesc.Texture2DArray.FirstArraySlice = 1; + viewDesc.Texture2DArray.ArraySize = 1; + + winrt::com_ptr view; + EXPECT_HRESULT_SUCCEEDED(device->CreateRenderTargetView(texture.get(), &viewDesc, view.put())); + return {texture, view}; + } + + DepthTexture CreateArrayMipDepthTexture(ID3D11Device* device) + { + D3D11_TEXTURE2D_DESC desc{}; + desc.Width = 64; + desc.Height = 32; + desc.MipLevels = 3; + desc.ArraySize = 3; + desc.Format = DXGI_FORMAT_R24G8_TYPELESS; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + + winrt::com_ptr texture; + EXPECT_HRESULT_SUCCEEDED(device->CreateTexture2D(&desc, nullptr, texture.put())); + + D3D11_DEPTH_STENCIL_VIEW_DESC viewDesc{}; + viewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY; + viewDesc.Flags = D3D11_DSV_READ_ONLY_DEPTH | D3D11_DSV_READ_ONLY_STENCIL; + viewDesc.Texture2DArray.MipSlice = 2; + viewDesc.Texture2DArray.FirstArraySlice = 1; + viewDesc.Texture2DArray.ArraySize = 1; + + winrt::com_ptr view; + EXPECT_HRESULT_SUCCEEDED(device->CreateDepthStencilView(texture.get(), &viewDesc, view.put())); + return {texture, view}; + } + + DepthTexture CreateWindowDepthTexture(ID3D11Device* device, const D3D11_DEPTH_STENCIL_VIEW_DESC& viewDesc, uint32_t arraySize = 1) + { + D3D11_TEXTURE2D_DESC desc{}; + desc.Width = 32; + desc.Height = 24; + desc.MipLevels = 2; + desc.ArraySize = arraySize; + desc.Format = DXGI_FORMAT_R24G8_TYPELESS; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; + + winrt::com_ptr texture; + EXPECT_HRESULT_SUCCEEDED(device->CreateTexture2D(&desc, nullptr, texture.put())); + winrt::com_ptr view; + EXPECT_HRESULT_SUCCEEDED(device->CreateDepthStencilView(texture.get(), &viewDesc, view.put())); + return {texture, view}; + } + + Babylon::Graphics::DeviceContext& GetContext(Babylon::Graphics::Device& device, Babylon::AppRuntime& runtime) + { + std::promise result; + auto future = result.get_future(); + runtime.Dispatch([&](Napi::Env env) { + device.AddToJavaScript(env); + result.set_value(&Babylon::Graphics::DeviceContext::GetFromJavaScript(env)); + }); + return *future.get(); + } + + std::vector ClearAndCapture( + Babylon::Graphics::Device& device, + Babylon::Graphics::DeviceContext& context, + uint32_t color) + { + auto captured = std::make_shared>>(); + context.RequestScreenShot([captured](auto pixels) { captured->emplace(std::move(pixels)); }); + for (size_t frame = 0; frame < 3 && !captured->has_value(); ++frame) + { + device.StartRenderingCurrentFrame(); + Babylon::Graphics::FrameBuffer backBuffer{context, BGFX_INVALID_HANDLE, 0, 0, true, true, true}; + backBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, color, 1.0f, 0); + device.FinishRenderingCurrentFrame(); + } + if (!captured->has_value()) + { + throw std::runtime_error{"External back buffer screenshot did not complete within three frames."}; + } + return std::move(captured->value()); + } + + void ApplyPendingBackBufferUpdate(Babylon::Graphics::Device& device) + { + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + } + + void ExpectSolidColor( + const std::vector& pixels, + uint32_t width, + uint32_t height, + uint32_t color) + { + ASSERT_EQ(pixels.size(), static_cast(width) * height * 4); + const std::array expected{ + static_cast(color >> 24), + static_cast(color >> 16), + static_cast(color >> 8), + static_cast(color)}; + for (size_t index = 0; index < pixels.size(); ++index) + { + if (pixels[index] != expected[index % expected.size()]) + { + ADD_FAILURE() << "Unexpected channel at byte " << index << ": " + << unsigned(pixels[index]) << ", expected " + << unsigned(expected[index % expected.size()]); + break; + } + } + } + + void ClearArrayMip( + ID3D11Device* device, + ID3D11Texture2D* texture, + uint32_t arraySlice, + const std::array& color) + { + D3D11_RENDER_TARGET_VIEW_DESC viewDesc{}; + viewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + viewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY; + viewDesc.Texture2DArray.MipSlice = 2; + viewDesc.Texture2DArray.FirstArraySlice = arraySlice; + viewDesc.Texture2DArray.ArraySize = 1; + + winrt::com_ptr view; + ASSERT_HRESULT_SUCCEEDED(device->CreateRenderTargetView(texture, &viewDesc, view.put())); + winrt::com_ptr context; + device->GetImmediateContext(context.put()); + context->ClearRenderTargetView(view.get(), color.data()); + } + + std::vector ReadArrayMip(ID3D11Device* device, ID3D11Texture2D* texture, uint32_t arraySlice) + { + D3D11_TEXTURE2D_DESC stagingDesc{}; + stagingDesc.Width = 16; + stagingDesc.Height = 8; + stagingDesc.MipLevels = 1; + stagingDesc.ArraySize = 1; + stagingDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + stagingDesc.SampleDesc.Count = 1; + stagingDesc.Usage = D3D11_USAGE_STAGING; + stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + winrt::com_ptr staging; + EXPECT_HRESULT_SUCCEEDED(device->CreateTexture2D(&stagingDesc, nullptr, staging.put())); + winrt::com_ptr context; + device->GetImmediateContext(context.put()); + context->CopySubresourceRegion( + staging.get(), + 0, + 0, + 0, + 0, + texture, + D3D11CalcSubresource(2, arraySlice, 3), + nullptr); + + D3D11_MAPPED_SUBRESOURCE mapped{}; + EXPECT_HRESULT_SUCCEEDED(context->Map(staging.get(), 0, D3D11_MAP_READ, 0, &mapped)); + std::vector pixels(stagingDesc.Width * stagingDesc.Height * 4); + for (uint32_t row = 0; row < stagingDesc.Height; ++row) + { + memcpy( + pixels.data() + row * stagingDesc.Width * 4, + static_cast(mapped.pData) + row * mapped.RowPitch, + stagingDesc.Width * 4); + } + context->Unmap(staging.get(), 0); + return pixels; + } +} + +TEST(Device, ExternalBackBufferCaptureAndUpdate) +{ + winrt::com_ptr d3dDevice = CreateDevice(); + auto first = CreateTestRenderTargetTexture( + d3dDevice.get(), + 64, + 48, + 1, + DXGI_FORMAT_B8G8R8A8_UNORM); + + Babylon::Graphics::Configuration config{}; + config.Device = d3dDevice.get(); + config.BackBufferColor = first.View.get(); + config.Width = 64; + config.Height = 48; + + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + + first = {}; + ExpectSolidColor(ClearAndCapture(device, context, 0x2050a0ff), 64, 48, 0x2050a0ff); + + auto second = CreateTestRenderTargetTexture(d3dDevice.get(), 32, 24); + device.UpdateBackBuffer(second.View.get()); + device.UpdateSize(32, 24); + second = {}; + ApplyPendingBackBufferUpdate(device); + + ExpectSolidColor(ClearAndCapture(device, context, 0x804020ff), 32, 24, 0x804020ff); +} + +TEST(Device, BackBufferPreservesViewSubresourceFormatAndDepthFlags) +{ + winrt::com_ptr d3dDevice = CreateDevice(); + auto color = CreateArrayMipRenderTarget(d3dDevice.get()); + auto depth = CreateArrayMipDepthTexture(d3dDevice.get()); + + ClearArrayMip(d3dDevice.get(), color.Texture.get(), 0, {0.0f, 1.0f, 0.0f, 1.0f}); + ClearArrayMip(d3dDevice.get(), color.Texture.get(), 1, {0.0f, 0.0f, 1.0f, 1.0f}); + ClearArrayMip(d3dDevice.get(), color.Texture.get(), 2, {0.0f, 1.0f, 0.0f, 1.0f}); + + Babylon::Graphics::Configuration config{}; + config.Device = d3dDevice.get(); + config.BackBufferColor = color.View.get(); + config.BackBufferDepthStencil = depth.View.get(); + config.Width = 16; + config.Height = 8; + + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + ExpectSolidColor(ClearAndCapture(device, context, 0xff0000ff), 16, 8, 0xff0000ff); + + ExpectSolidColor(ReadArrayMip(d3dDevice.get(), color.Texture.get(), 0), 16, 8, 0x00ff00ff); + ExpectSolidColor(ReadArrayMip(d3dDevice.get(), color.Texture.get(), 1), 16, 8, 0xff0000ff); + ExpectSolidColor(ReadArrayMip(d3dDevice.get(), color.Texture.get(), 2), 16, 8, 0x00ff00ff); +} + +TEST(Device, BackBufferMsaaCaptureUsesActualViewSampleCount) +{ + winrt::com_ptr d3dDevice = CreateDevice(); + uint32_t qualityLevels{}; + ASSERT_HRESULT_SUCCEEDED( + d3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, &qualityLevels)); + if (qualityLevels == 0) + { + GTEST_SKIP() << "D3D11 WARP does not support 4x MSAA for RGBA8."; + } + + auto color = CreateTestRenderTargetTexture(d3dDevice.get(), 32, 24, 4); + Babylon::Graphics::Configuration config{}; + config.Device = d3dDevice.get(); + config.BackBufferColor = color.View.get(); + config.Width = 32; + config.Height = 24; + config.BackBufferDepthStencilFormat = Babylon::Graphics::DepthStencilFormat::Depth24Stencil8; + + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); +} + +TEST(Device, WindowDepthBackBufferRejectsReadOnlyViews) +{ + auto d3dDevice = CreateDevice(); + for (UINT flags : std::array{D3D11_DSV_READ_ONLY_DEPTH, D3D11_DSV_READ_ONLY_STENCIL, + D3D11_DSV_READ_ONLY_DEPTH | D3D11_DSV_READ_ONLY_STENCIL}) + { + SCOPED_TRACE(flags); + D3D11_DEPTH_STENCIL_VIEW_DESC viewDesc{}; + viewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + viewDesc.Flags = flags; + auto depth = CreateWindowDepthTexture(d3dDevice.get(), viewDesc); + + auto config = g_deviceConfig; + config.Device = d3dDevice.get(); + config.Width = 32; + config.Height = 24; + config.BackBufferColor = nullptr; + config.BackBufferDepthStencil = depth.View.get(); + Babylon::Graphics::Device device{config}; + EXPECT_THROW(device.EnableRendering(), std::runtime_error); + } +} + +TEST(Device, WindowDepthBackBufferRejectsNonDefaultSubresources) +{ + auto d3dDevice = CreateDevice(); + for (uint32_t arraySize : {0u, 1u, 2u}) + { + SCOPED_TRACE(arraySize); + const bool arrayView = arraySize != 0; + D3D11_DEPTH_STENCIL_VIEW_DESC viewDesc{}; + viewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + viewDesc.ViewDimension = arrayView ? D3D11_DSV_DIMENSION_TEXTURE2DARRAY : D3D11_DSV_DIMENSION_TEXTURE2D; + if (arrayView) + { + viewDesc.Texture2DArray.FirstArraySlice = arraySize - 1; + viewDesc.Texture2DArray.ArraySize = 1; + } + else + { + viewDesc.Texture2D.MipSlice = 1; + } + auto depth = CreateWindowDepthTexture(d3dDevice.get(), viewDesc, arrayView ? arraySize : 1); + + auto config = g_deviceConfig; + config.Device = d3dDevice.get(); + config.Width = arrayView ? 32 : 16; + config.Height = arrayView ? 24 : 12; + config.BackBufferColor = nullptr; + config.BackBufferDepthStencil = depth.View.get(); + Babylon::Graphics::Device device{config}; + EXPECT_THROW(device.EnableRendering(), std::runtime_error); + } +} + +TEST(Device, WindowDepthBackBufferAcceptsDefaultViewAfterRejection) +{ + auto d3dDevice = CreateDevice(); + D3D11_DEPTH_STENCIL_VIEW_DESC viewDesc{}; + viewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + viewDesc.Flags = D3D11_DSV_READ_ONLY_DEPTH; + auto rejected = CreateWindowDepthTexture(d3dDevice.get(), viewDesc); + viewDesc.Flags = 0; + auto depth = CreateWindowDepthTexture(d3dDevice.get(), viewDesc); + + auto config = g_deviceConfig; + config.Device = d3dDevice.get(); + config.Width = 32; + config.Height = 24; + config.BackBufferColor = nullptr; + config.BackBufferDepthStencil = rejected.View.get(); + Babylon::Graphics::Device device{config}; + EXPECT_THROW(device.EnableRendering(), std::runtime_error); + device.UpdateBackBuffer(nullptr, depth.View.get()); + ASSERT_NO_THROW(device.EnableRendering()); + + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); +} diff --git a/Apps/UnitTests/Source/Tests.Device.D3D11.cpp b/Apps/UnitTests/Source/Tests.Device.D3D11.cpp index d91427eebf..59e6b61941 100644 --- a/Apps/UnitTests/Source/Tests.Device.D3D11.cpp +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.cpp @@ -6,6 +6,10 @@ #include +#include +#include +#include + extern Babylon::Graphics::Configuration g_deviceConfig; namespace diff --git a/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp b/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp new file mode 100644 index 0000000000..a08b7aca34 --- /dev/null +++ b/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp @@ -0,0 +1,242 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif +#if defined(_WIN32) && defined(BABYLON_NATIVE_GRAPHICS_API_D3D12) +#include +#include +#endif + +extern Babylon::Graphics::Configuration g_deviceConfig; + +#if !defined(USE_NOOP_METAL_DEVICE) +namespace +{ + Babylon::Graphics::DeviceContext& GetContext(Babylon::Graphics::Device& device, Babylon::AppRuntime& runtime) + { + std::promise result; + auto future = result.get_future(); + runtime.Dispatch([&](Napi::Env env) { + device.AddToJavaScript(env); + result.set_value(&Babylon::Graphics::DeviceContext::GetFromJavaScript(env)); + }); + return *future.get(); + } + + std::vector ClearAndCapture( + Babylon::Graphics::Device& device, Babylon::Graphics::DeviceContext& context, uint32_t color, + const std::function& render = {}) + { + auto captured = std::make_shared>>(); + context.RequestScreenShot([captured](auto pixels) { captured->emplace(std::move(pixels)); }); + for (size_t frame = 0; frame < 3 && !captured->has_value(); ++frame) + { + device.StartRenderingCurrentFrame(); + Babylon::Graphics::FrameBuffer backBuffer{context, BGFX_INVALID_HANDLE, 0, 0, true, true, true}; + backBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, color, 1.0f, 0); + if (render) + { + render(*context.GetActiveEncoder()); + } + device.FinishRenderingCurrentFrame(); + } + if (!captured->has_value()) + { + throw std::runtime_error{"Window screenshot did not complete within three frames."}; + } + return std::move(captured->value()); + } + + void ExpectSolidColor(const std::vector& pixels, uint32_t width, uint32_t height, uint32_t color) + { + ASSERT_EQ(pixels.size(), static_cast(width) * height * 4); + const std::array expected{ + static_cast(color >> 24), static_cast(color >> 16), + static_cast(color >> 8), static_cast(color)}; + for (size_t i = 0; i < pixels.size(); ++i) + { + if (pixels[i] != expected[i % 4]) + { + ADD_FAILURE() << "Unexpected channel at byte " << i << ": " << unsigned(pixels[i]) + << ", expected " << unsigned(expected[i % 4]); + break; + } + } + } +} + +TEST(Device, ExplicitDefaultFrameBufferPreservesTarget) +{ + auto config = g_deviceConfig; + config.Width = 32; + config.Height = 24; + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + Babylon::Graphics::FrameBuffer window{context, BGFX_INVALID_HANDLE, 0, 0, true, true, true}; + ExpectSolidColor(ClearAndCapture(device, context, 0xff0000ff), 32, 24, 0xff0000ff); + EXPECT_EQ(window.Handle().idx, context.GetBackBufferHandle().idx); + + for (bool defaultBackBuffer : {false, true}) + { + const auto handle = bgfx::createFrameBuffer(32, 24, bgfx::TextureFormat::RGBA8); + ASSERT_TRUE(bgfx::isValid(handle)); + Babylon::Graphics::FrameBuffer target{context, handle, 32, 24, defaultBackBuffer, false, false}; + EXPECT_EQ(target.DefaultBackBuffer(), defaultBackBuffer); + EXPECT_EQ(target.Handle().idx, handle.idx); + EXPECT_NE(target.Handle().idx, window.Handle().idx); + + ExpectSolidColor(ClearAndCapture(device, context, 0xff0000ff, [&](bgfx::Encoder& encoder) { + target.Clear(encoder, BGFX_CLEAR_COLOR, 0x00ff00ff, 1.0f, 0); + }), 32, 24, 0xff0000ff); + + target.Dispose(); + EXPECT_FALSE(bgfx::isValid(target.Handle())); + } +} + +TEST(Device, SwapChainResizeAndMsaaPreserveCapture) +{ + for (auto depth : {Babylon::Graphics::DepthStencilFormat::None, + Babylon::Graphics::DepthStencilFormat::Depth32, + Babylon::Graphics::DepthStencilFormat::Depth24Stencil8}) + { + auto config = g_deviceConfig; + config.Width = 64; + config.Height = 48; + config.BackBufferDepthStencilFormat = depth; + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + ExpectSolidColor(ClearAndCapture(device, context, 0x2050a0ff), 64, 48, 0x2050a0ff); + const auto handle = context.GetBackBufferHandle(); + ASSERT_TRUE(bgfx::isValid(handle)); + + for (uint8_t samples : std::array{1, 2, 4, 1}) + { + device.UpdateSize(32, 24); + device.UpdateMSAA(samples); + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + EXPECT_EQ(context.GetBackBufferHandle().idx, handle.idx); + ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); + } + } +} + +TEST(Device, SwapChainIsRecreatedAfterRenderingIsReenabled) +{ + auto config = g_deviceConfig; + config.Width = 32; + config.Height = 24; + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + ExpectSolidColor(ClearAndCapture(device, context, 0xff0000ff), 32, 24, 0xff0000ff); + const auto id = context.GetDeviceId(); + device.DisableRendering(); + ExpectSolidColor(ClearAndCapture(device, context, 0x00ff00ff), 32, 24, 0x00ff00ff); + EXPECT_NE(context.GetDeviceId(), id); +} + +#if defined(_WIN32) && defined(BABYLON_NATIVE_GRAPHICS_API_D3D12) && defined(__ID3D12InfoQueue1_INTERFACE_DEFINED__) +namespace +{ + void WINAPI CountD3D12Errors(D3D12_MESSAGE_CATEGORY, D3D12_MESSAGE_SEVERITY severity, + D3D12_MESSAGE_ID, LPCSTR, void* context) + { + if (severity == D3D12_MESSAGE_SEVERITY_ERROR || severity == D3D12_MESSAGE_SEVERITY_CORRUPTION) + { + static_cast(context)->fetch_add(1, std::memory_order_relaxed); + } + } +} + +TEST(Device, SwapChainMsaaUsesValidResourceStates) +{ + auto config = g_deviceConfig; + config.Width = 32; + config.Height = 24; + config.MSAASamples = 1; + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); + winrt::com_ptr infoQueue; + const auto result = device.GetPlatformInfo().Device->QueryInterface(IID_PPV_ARGS(infoQueue.put())); + if (FAILED(result)) + { + GTEST_SKIP() << "D3D12 debug-layer callbacks are unavailable: " << result; + } + + std::atomic_uint32_t errors{}; + DWORD cookie{}; + winrt::check_hresult(infoQueue->RegisterMessageCallback( + CountD3D12Errors, D3D12_MESSAGE_CALLBACK_FLAG_NONE, &errors, &cookie)); + const auto unregister = gsl::finally([&] { infoQueue->UnregisterMessageCallback(cookie); }); + + for (uint8_t samples : std::array{4, 1, 2, 4}) + { + device.UpdateMSAA(samples); + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); + } + EXPECT_EQ(errors.load(std::memory_order_relaxed), 0u); +} +#endif + +#ifdef _WIN32 +TEST(Device, ReplacingWindowPreservesDeviceAndTargetsNewSurface) +{ + HWND first = CreateWindowExW(0, L"STATIC", L"First surface", WS_POPUP, 0, 0, 64, 48, nullptr, nullptr, GetModuleHandleW(nullptr), nullptr); + HWND second = CreateWindowExW(0, L"STATIC", L"Second surface", WS_POPUP, 0, 0, 64, 48, nullptr, nullptr, GetModuleHandleW(nullptr), nullptr); + const auto cleanup = gsl::finally([&] { + if (first) + { + DestroyWindow(first); + } + if (second) + { + DestroyWindow(second); + } + }); + ASSERT_NE(first, nullptr); + ASSERT_NE(second, nullptr); + + auto config = g_deviceConfig; + config.Window = first; + config.Width = 32; + config.Height = 24; + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + ExpectSolidColor(ClearAndCapture(device, context, 0xff0000ff), 32, 24, 0xff0000ff); + const auto id = context.GetDeviceId(); + + device.UpdateWindow(second); + device.UpdateSize(48, 36); + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + ASSERT_TRUE(DestroyWindow(first)); + first = nullptr; + + EXPECT_EQ(context.GetDeviceId(), id); + ExpectSolidColor(ClearAndCapture(device, context, 0x0000ffff), 48, 36, 0x0000ffff); +} +#endif +#endif diff --git a/Apps/UnitTests/Source/Tests.ExternalTexture.Lifecycle.cpp b/Apps/UnitTests/Source/Tests.ExternalTexture.Lifecycle.cpp index 6f35722eb6..561f07ca21 100644 --- a/Apps/UnitTests/Source/Tests.ExternalTexture.Lifecycle.cpp +++ b/Apps/UnitTests/Source/Tests.ExternalTexture.Lifecycle.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include +#include extern Babylon::Graphics::Configuration g_deviceConfig; @@ -137,3 +139,111 @@ TEST(ExternalTexture, JavaScriptDisposeThinTexturePreventsUpdate) TestJavaScriptDisposePreventsUpdate(true); #endif } + +TEST(ExternalTexture, NativeImportRetainsOwnerThroughHandleDestruction) +{ + for (bool afterRender : {false, true}) + { + for (uint32_t action : {0u, 1u, 2u}) + { + SCOPED_TRACE(afterRender); + SCOPED_TRACE(action); + Babylon::Graphics::Device device{g_deviceConfig}; + Babylon::AppRuntime runtime{}; + std::promise result; + auto future = result.get_future(); + runtime.Dispatch([&](Napi::Env env) { + device.AddToJavaScript(env); + result.set_value(&Babylon::Graphics::DeviceContext::GetFromJavaScript(env)); + }); + auto& context = *future.get(); + device.StartRenderingCurrentFrame(); + + if (bgfx::getRendererType() != bgfx::RendererType::Direct3D11 && + bgfx::getRendererType() != bgfx::RendererType::Metal) + { + device.FinishRenderingCurrentFrame(); + GTEST_SKIP() << "Native pointer import coverage requires D3D11 or Metal."; + } + + auto native = std::shared_ptr{ + Helpers::CreateTexture(device.GetPlatformInfo().Device, 4, 4), + Helpers::DestroyTexture}; + std::weak_ptr retained = native; + std::weak_ptr replacement; + auto texture = std::make_unique(context); + texture->Create2D(4, 4, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE, + reinterpret_cast(native.get()), native); + + // Model Close releasing the producer while the caller keeps its wrapper. + native.reset(); + device.FinishRenderingCurrentFrame(); + for (uint32_t frame = 0; frame < 2; ++frame) + { + EXPECT_TRUE(texture->IsValid()); + EXPECT_FALSE(retained.expired()); + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + } + EXPECT_FALSE(retained.expired()); + + device.StartRenderingCurrentFrame(); + auto disposeOrReplace = [&] { + if (action == 0) + { + texture->Dispose(); + } + else if (action == 1) + { + texture.reset(); + } + else + { + // Replacing the producer's output must not release the old import. + native = std::shared_ptr{ + Helpers::CreateTexture(device.GetPlatformInfo().Device, 8, 8), + Helpers::DestroyTexture}; + EXPECT_FALSE(retained.expired()); + replacement = native; + texture->Create2D(8, 8, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE, + reinterpret_cast(native.get()), native); + native.reset(); + } + EXPECT_FALSE(retained.expired()); + }; + if (afterRender) + { + arcana::make_task(context.AfterRenderScheduler(), arcana::cancellation::none(), disposeOrReplace); + } + else + { + disposeOrReplace(); + } + device.FinishRenderingCurrentFrame(); + + if (afterRender) + { + EXPECT_FALSE(retained.expired()); + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + } + EXPECT_TRUE(retained.expired()); + if (action == 0) + { + EXPECT_FALSE(texture->IsValid()); + } + else if (action == 2) + { + EXPECT_TRUE(texture->IsValid()); + EXPECT_EQ(texture->Width(), 8); + EXPECT_EQ(texture->Height(), 8); + EXPECT_FALSE(replacement.expired()); + texture.reset(); + EXPECT_FALSE(replacement.expired()); + device.StartRenderingCurrentFrame(); + device.FinishRenderingCurrentFrame(); + EXPECT_TRUE(replacement.expired()); + } + } + } +} diff --git a/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp b/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp index d6711c5674..f65978ea2c 100644 --- a/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp +++ b/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp @@ -5,6 +5,9 @@ #include #include #include +#ifdef HAS_SHADER_COMPILER +#include +#endif #include #include @@ -17,6 +20,28 @@ using namespace std::chrono_literals; extern Babylon::Graphics::Configuration g_deviceConfig; +#ifdef HAS_SHADER_COMPILER +TEST(ShaderCompilation, NativeCompilerAcceptsExistingVec4UniformArray) +{ + Babylon::Plugins::ShaderCompiler compiler{}; + auto shader = compiler.Compile( + R"( + in vec2 position; + void main() { gl_Position = vec4(position, 0.0, 1.0); } + )", + R"( + precision highp float; + uniform vec4 values[2]; + layout(location = 0) out vec4 fragColor; + vec4 readValue() { return values[0]; } + void main() { fragColor = readValue(); } + )"); + + EXPECT_FALSE(shader.VertexBytes.empty()); + EXPECT_FALSE(shader.FragmentBytes.empty()); +} +#endif + TEST(ShaderCompilation, CompileComprehensiveGLSL) { Babylon::Graphics::Device device{g_deviceConfig}; diff --git a/CMakeLists.txt b/CMakeLists.txt index e177a666f0..358f49a4b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ FetchContent_Declare(base-n EXCLUDE_FROM_ALL) FetchContent_Declare(bgfx.cmake GIT_REPOSITORY https://github.com/BabylonJS/bgfx.cmake.git - GIT_TAG edf5822dd98a21cf52bfc4a940cd9ad5faba9633 + GIT_TAG 2e8efe6186d1bc311674153c8207c19774bc77a9 EXCLUDE_FROM_ALL) FetchContent_Declare(CMakeExtensions GIT_REPOSITORY https://github.com/BabylonJS/CMakeExtensions.git diff --git a/Core/Graphics/CMakeLists.txt b/Core/Graphics/CMakeLists.txt index 20b7355ce6..157d567de8 100644 --- a/Core/Graphics/CMakeLists.txt +++ b/Core/Graphics/CMakeLists.txt @@ -43,7 +43,6 @@ target_compile_definitions(Graphics target_link_libraries(Graphics PRIVATE JsRuntimeInternal PRIVATE bgfx - PRIVATE minz PRIVATE bx) if(WINDOWS_STORE) diff --git a/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h b/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h index 2066e33a81..7e2aeacaab 100644 --- a/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h +++ b/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h @@ -35,7 +35,8 @@ namespace Babylon::Graphics BackBufferColorT BackBufferColor{}; // Depth stencil back buffer to use instead of creating one internally. - // @remarks Only available for D3D11. DepthStencilFormat is ignored when specified. + // @remarks Only available for D3D11. BackBufferDepthStencilFormat is ignored when specified. + // With a window and no BackBufferColor, only writable, non-array, mip-0 views are supported. BackBufferDepthStencilT BackBufferDepthStencil{}; #endif @@ -96,6 +97,7 @@ namespace Babylon::Graphics // Features and functionalities will be added and // method and structure might change. + // Switches the rendering surface at the next frame boundary without recreating the device. void UpdateWindow(WindowT window); // Sets the underlying graphics device used for rendering. The new device takes effect on @@ -111,6 +113,8 @@ namespace Babylon::Graphics void UpdateAlphaPremultiplied(bool enabled); #ifdef GRAPHICS_BACK_BUFFER_SUPPORT + // Retains the supplied views while in use. Changes take effect at the next frame boundary. + // The same depth-view restrictions as Configuration::BackBufferDepthStencil apply. void UpdateBackBuffer(BackBufferColorT backBufferColor, BackBufferDepthStencilT backBufferDepthStencil = {}); #endif diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/BgfxCallback.h b/Core/Graphics/InternalInclude/Babylon/Graphics/BgfxCallback.h index c6fd21fa04..77868b0503 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/BgfxCallback.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/BgfxCallback.h @@ -25,6 +25,8 @@ namespace Babylon::Graphics virtual ~BgfxCallback() = default; void AddScreenShotCallback(std::function)> callback); + void CaptureNextScreenShot(); + void CompleteScreenShot(const CaptureData& data); void SetDiagnosticOutput(std::function outputFunction); void trace(const char* _filePath, uint16_t _line, const char* _format, ...); @@ -46,6 +48,7 @@ namespace Babylon::Graphics std::function m_outputFunction; std::queue)>> m_screenShotCallbacks; + bool m_captureScreenShot{}; CaptureData m_captureData{}; const std::function m_captureCallback{}; diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h b/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h index bc5e15411d..6d1456df4d 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h @@ -113,6 +113,7 @@ namespace Babylon::Graphics //Note: This is an index that changes when bgfx gets reset. It should be used to validate that resource handles created using bgfx remain valid on destruction. uintptr_t GetDeviceId() const; + bgfx::FrameBufferHandle GetBackBufferHandle() const; using CaptureCallbackTicketT = arcana::ticketed_collection>::ticket; CaptureCallbackTicketT AddCaptureCallback(std::function callback); diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceQueries.h b/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceQueries.h index 51192c6287..1593e3d30a 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceQueries.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceQueries.h @@ -1,6 +1,12 @@ #pragma once #include +#include + +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT +#include +#include +#endif namespace Babylon::Graphics { @@ -15,4 +21,17 @@ namespace Babylon::Graphics // Lives in InternalInclude/ to keep it off the public surface; // reachable to in-tree consumers via the GraphicsDeviceContext target. float GetDevicePixelRatio(WindowT window); + +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + namespace D3D11TextureFormats + { + struct BgfxTextureFormat + { + bgfx::TextureFormat::Enum Format; + bool Srgb; + }; + + std::optional TryGetBgfxTextureFormat(DXGI_FORMAT format); + } +#endif } diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h b/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h index 46abc9ad08..e6c33d455f 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h @@ -57,6 +57,7 @@ namespace Babylon::Graphics const uint16_t m_width{}; const uint16_t m_height{}; const bool m_defaultBackBuffer{}; + const bool m_useDeviceBackBuffer{}; const bool m_hasDepth{}; const bool m_hasStencil{}; diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h index 1bd48809ed..a0454c1dc8 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace Babylon::Graphics { @@ -20,7 +21,8 @@ namespace Babylon::Graphics bool IsValid() const; - void Create2D(uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags, uintptr_t nativeTextureHandle = 0); + // nativeTextureOwner retains a borrowed native resource through disposal and its destruction frame. + void Create2D(uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags, uintptr_t nativeTextureHandle = 0, std::shared_ptr nativeTextureOwner = {}); void Update2D(uint16_t layer, uint8_t mip, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const bgfx::Memory* mem, uint16_t pitch = UINT16_MAX); void Create3D(uint16_t width, uint16_t height, uint16_t depth, bool hasMips, bgfx::TextureFormat::Enum format, uint64_t flags); @@ -78,6 +80,7 @@ namespace Babylon::Graphics bgfx::TextureHandle m_handle{bgfx::kInvalidHandle}; bool m_ownsHandle{false}; + std::shared_ptr m_nativeTextureOwner{}; uint16_t m_width{0}; uint16_t m_height{0}; bool m_hasMips{false}; diff --git a/Core/Graphics/Source/BgfxCallback.cpp b/Core/Graphics/Source/BgfxCallback.cpp index dac2144d75..037bd180ad 100644 --- a/Core/Graphics/Source/BgfxCallback.cpp +++ b/Core/Graphics/Source/BgfxCallback.cpp @@ -28,6 +28,16 @@ namespace Babylon::Graphics m_screenShotCallbacks.emplace(std::move(callback)); } + void BgfxCallback::CaptureNextScreenShot() + { + m_captureScreenShot = true; + } + + void BgfxCallback::CompleteScreenShot(const CaptureData& data) + { + screenShot("", data.Width, data.Height, data.Pitch, data.Format, data.Data, data.DataSize, data.YFlip); + } + void BgfxCallback::SetDiagnosticOutput(std::function outputFunction) { m_outputFunction = std::move(outputFunction); @@ -116,9 +126,18 @@ namespace Babylon::Graphics { } - void BgfxCallback::screenShot(const char* /*filePath*/, uint32_t width, uint32_t height, uint32_t pitch, bgfx::TextureFormat::Enum format, const void* data, uint32_t /*size*/, bool yflip) + void BgfxCallback::screenShot(const char* /*filePath*/, uint32_t width, uint32_t height, uint32_t pitch, bgfx::TextureFormat::Enum format, const void* data, uint32_t size, bool yflip) { - assert(!m_screenShotCallbacks.empty()); // addScreenShotCallback not called before doing the screenshot call on bgfx + assert(m_captureScreenShot || !m_screenShotCallbacks.empty()); + if (m_captureScreenShot) + { + m_captureScreenShot = false; + m_captureCallback(CaptureData{width, height, pitch, format, yflip, data, size}); + } + if (m_screenShotCallbacks.empty()) + { + return; + } std::vector array(width * height * 4); // do not use pitch to define output size because it's padded uint8_t* bitmap{array.data()}; @@ -158,8 +177,13 @@ namespace Babylon::Graphics throw std::runtime_error{"Unsupported format for screenshot"}; } - m_screenShotCallbacks.front()(std::move(array)); - m_screenShotCallbacks.pop(); + const auto count = m_screenShotCallbacks.size(); + for (size_t i = 0; i < count; ++i) + { + auto callback = std::move(m_screenShotCallbacks.front()); + m_screenShotCallbacks.pop(); + callback(i + 1 == count ? std::move(array) : array); + } } void BgfxCallback::captureBegin(uint32_t width, uint32_t height, uint32_t pitch, bgfx::TextureFormat::Enum format, bool yflip) diff --git a/Core/Graphics/Source/DeviceContext.cpp b/Core/Graphics/Source/DeviceContext.cpp index ca4ac320f1..18505bcfa4 100644 --- a/Core/Graphics/Source/DeviceContext.cpp +++ b/Core/Graphics/Source/DeviceContext.cpp @@ -175,4 +175,9 @@ namespace Babylon::Graphics { return m_graphicsImpl.GetId(); } + + bgfx::FrameBufferHandle DeviceContext::GetBackBufferHandle() const + { + return m_graphicsImpl.GetBackBufferHandle(); + } } diff --git a/Core/Graphics/Source/DeviceImpl.cpp b/Core/Graphics/Source/DeviceImpl.cpp index 80011dc0c2..ea9bc9794f 100644 --- a/Core/Graphics/Source/DeviceImpl.cpp +++ b/Core/Graphics/Source/DeviceImpl.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -101,11 +103,11 @@ namespace Babylon::Graphics #endif // - // init.resolution + // init.swapChain // - init.resolution.reset = BGFX_RESET_VSYNC | BGFX_RESET_MAXANISOTROPY | BGFX_RESET_FLIP_AFTER_RENDER; - init.resolution.maxFrameLatency = 1; + init.reset = BGFX_RESET_VSYNC | BGFX_RESET_MAXANISOTROPY | BGFX_RESET_FLIP_AFTER_RENDER; + init.swapChain.maxFrameLatency = 1; UpdateSize(config.Width, config.Height); UpdateMSAA(config.MSAASamples); @@ -114,15 +116,15 @@ namespace Babylon::Graphics switch (config.BackBufferDepthStencilFormat) { case DepthStencilFormat::None: - init.resolution.formatDepthStencil = bgfx::TextureFormat::UnknownDepth; + init.swapChain.formatDepthStencil = bgfx::TextureFormat::Count; break; case DepthStencilFormat::Depth32: // D32 has no DSV mapping on D3D11/D12 in current bgfx; D32F does. - init.resolution.formatDepthStencil = bgfx::TextureFormat::D32F; + init.swapChain.formatDepthStencil = bgfx::TextureFormat::D32F; break; case DepthStencilFormat::Depth24Stencil8: default: - init.resolution.formatDepthStencil = bgfx::TextureFormat::D24S8; + init.swapChain.formatDepthStencil = bgfx::TextureFormat::D24S8; break; } } @@ -137,12 +139,22 @@ namespace Babylon::Graphics return m_bgfxId; } + bgfx::FrameBufferHandle DeviceImpl::GetBackBufferHandle() const + { +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + if (bgfx::isValid(m_externalBackBuffer.FrameBuffer)) + { + return m_externalBackBuffer.FrameBuffer; + } +#endif + return m_windowFrameBuffer; + } + void DeviceImpl::UpdateWindow(WindowT window) { std::scoped_lock lock{m_state.Mutex}; m_state.Window = window; - ConfigureBgfxPlatformData(m_state.Bgfx.InitState.platformData, window); - ConfigureBgfxRenderType(m_state.Bgfx.InitState.platformData, m_state.Bgfx.InitState.type); + ConfigureBgfxSwapChain(m_state.Bgfx.InitState.swapChain, window); m_state.Resolution.DevicePixelRatio = Babylon::Graphics::GetDevicePixelRatio(window); m_state.Bgfx.Dirty = true; } @@ -170,7 +182,7 @@ namespace Babylon::Graphics { std::scoped_lock lock{m_state.Mutex}; auto& init = m_state.Bgfx.InitState; - init.resolution.reset &= ~BGFX_RESET_MSAA_MASK; + init.swapChain.flags &= ~BGFX_SWAP_CHAIN_MSAA_MASK; switch (value) { case 0: @@ -178,16 +190,16 @@ namespace Babylon::Graphics // disable MSAA break; case 2: - init.resolution.reset |= BGFX_RESET_MSAA_X2; + init.swapChain.flags |= BGFX_SWAP_CHAIN_MSAA_X2; break; case 4: - init.resolution.reset |= BGFX_RESET_MSAA_X4; + init.swapChain.flags |= BGFX_SWAP_CHAIN_MSAA_X4; break; case 8: - init.resolution.reset |= BGFX_RESET_MSAA_X8; + init.swapChain.flags |= BGFX_SWAP_CHAIN_MSAA_X8; break; case 16: - init.resolution.reset |= BGFX_RESET_MSAA_X16; + init.swapChain.flags |= BGFX_SWAP_CHAIN_MSAA_X16; break; default: m_bgfxCallback.trace(__FILE__, __LINE__, "WARNING: Setting an incorrect value for SetMSAA (%d). Correct values are 0, 1 (disable MSAA) or 2, 4, 8, 16.", static_cast(value)); @@ -200,8 +212,8 @@ namespace Babylon::Graphics { std::scoped_lock lock{m_state.Mutex}; auto& init = m_state.Bgfx.InitState; - init.resolution.reset &= ~BGFX_RESET_TRANSPARENT_BACKBUFFER; - init.resolution.reset |= enabled ? BGFX_RESET_TRANSPARENT_BACKBUFFER : 0; + init.swapChain.flags &= ~BGFX_SWAP_CHAIN_TRANSPARENT_BACKBUFFER; + init.swapChain.flags |= enabled ? BGFX_SWAP_CHAIN_TRANSPARENT_BACKBUFFER : 0; m_state.Bgfx.Dirty = true; } @@ -209,8 +221,8 @@ namespace Babylon::Graphics void DeviceImpl::UpdateBackBuffer(BackBufferColorT backBufferColor, BackBufferDepthStencilT backBufferDepthStencil) { std::scoped_lock lock{m_state.Mutex}; - m_state.Bgfx.InitState.platformData.backBuffer = backBufferColor; - m_state.Bgfx.InitState.platformData.backBufferDS = backBufferDepthStencil; + m_state.BackBufferColor.copy_from(backBufferColor); + m_state.BackBufferDepthStencil.copy_from(backBufferDepthStencil); m_state.Bgfx.Dirty = true; } #endif @@ -251,15 +263,43 @@ namespace Babylon::Graphics // This tells bgfx to not create its own render thread. bgfx::renderFrame(); - // Initialize bgfx. - const auto& init{m_state.Bgfx.InitState}; + bool ready = false; + const auto rollback = gsl::finally([&] { + if (!ready) + { + if (m_cancellationSource) + { + m_cancellationSource->cancel(); + } + if (m_state.Bgfx.Initialized) + { + DestroyBackBuffer(); + bgfx::shutdown(); + m_state.Bgfx.Initialized = false; + ++m_bgfxId; + } + m_rendering = false; + m_renderThreadAffinity = {}; + } + }); + + // Select from the host surface; the headless bootstrap must not force Android to Noop. + ConfigureBgfxRenderType(m_state.Bgfx.InitState); + auto init{m_state.Bgfx.InitState}; + // Own the window framebuffer explicitly so reattaching a window does not + // recreate the device or leave bgfx presenting to the previous surface. + // Retain ndt so the rendering context uses the window's native display. + init.swapChain.nwh = nullptr; + init.swapChain.width = 0; + init.swapChain.height = 0; + init.swapChain.depth = BGFX_INVALID_HANDLE; if (!bgfx::init(init)) { throw std::runtime_error{"Failed to initialize bgfx."}; } m_state.Bgfx.Initialized = true; - m_state.Bgfx.Dirty = false; + UpdateBackBufferState(); m_cancellationSource.emplace(); @@ -270,6 +310,8 @@ namespace Babylon::Graphics m_renderResetCallback(); } } + m_state.Bgfx.Dirty = false; + ready = true; } } @@ -295,6 +337,7 @@ namespace Babylon::Graphics m_cancellationSource->cancel(); + DestroyBackBuffer(); bgfx::shutdown(); m_state.Bgfx.Initialized = false; m_bgfxId++; @@ -503,16 +546,6 @@ namespace Babylon::Graphics DeviceImpl::CaptureCallbackTicketT DeviceImpl::AddCaptureCallback(std::function callback) { - // If we're not already capturing, start. - { - std::scoped_lock lock{m_state.Mutex}; - if ((m_state.Bgfx.InitState.resolution.reset & BGFX_RESET_CAPTURE) == 0) - { - m_state.Bgfx.InitState.resolution.reset |= BGFX_RESET_CAPTURE; - m_state.Bgfx.Dirty = true; - } - } - return m_captureCallbacks.insert(std::move(callback), m_captureCallbacksMutex); } @@ -673,13 +706,12 @@ namespace Babylon::Graphics std::scoped_lock lock{m_state.Mutex}; if (m_state.Bgfx.Dirty) { - bgfx::setPlatformData(m_state.Bgfx.InitState.platformData); - // Discard the whole frame. bgfx::frame(BGFX_FRAME_DISCARD); - auto& res = m_state.Bgfx.InitState.resolution; - bgfx::reset(res.width, res.height, res.reset); + bgfx::reset(m_state.Bgfx.InitState.reset); + UpdateBackBufferState(); + const auto& res = m_state.Bgfx.InitState.swapChain; bgfx::setViewRect(0, 0, 0, static_cast(res.width), static_cast(res.height)); m_state.Bgfx.Dirty = false; @@ -689,7 +721,7 @@ namespace Babylon::Graphics void DeviceImpl::UpdateBgfxResolution() { std::scoped_lock lock{m_state.Mutex}; - auto& res = m_state.Bgfx.InitState.resolution; + auto& res = m_state.Bgfx.InitState.swapChain; auto level = m_state.Resolution.HardwareScalingLevel; res.width = static_cast(m_state.Resolution.Width / level); res.height = static_cast(m_state.Resolution.Height / level); @@ -701,19 +733,104 @@ namespace Babylon::Graphics ResizeRenderSurface(m_state.Window, res.width, res.height); } - void DeviceImpl::RequestScreenShots() + void DeviceImpl::DestroyBackBuffer() { + if (bgfx::isValid(m_windowFrameBuffer)) + { + bgfx::destroy(m_windowFrameBuffer); + m_windowFrameBuffer = BGFX_INVALID_HANDLE; + } + m_windowHandle = nullptr; + m_displayHandle = nullptr; +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + DestroyExternalBackBuffer(); +#endif + } + + void DeviceImpl::UpdateBackBufferState() + { + auto swapChain = m_state.Bgfx.InitState.swapChain; + swapChain.width = std::max(1u, swapChain.width); + swapChain.height = std::max(1u, swapChain.height); + +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + if (m_externalBackBuffer.Color || m_externalBackBuffer.Depth || m_state.BackBufferColor || m_state.BackBufferDepthStencil) + { + DestroyBackBuffer(); + // Release the old native swap chain before another one can bind its window. + bgfx::frame(BGFX_FRAME_DISCARD); + if (m_state.BackBufferColor || m_state.BackBufferDepthStencil) + { + CreateExternalBackBuffer(swapChain); + if (bgfx::isValid(m_externalBackBuffer.FrameBuffer)) + { + return; + } + swapChain.depth = m_externalBackBuffer.DepthHandle; + } + } +#endif + + if (bgfx::isValid(m_windowFrameBuffer) && + (m_windowHandle != swapChain.nwh || m_displayHandle != swapChain.ndt)) + { + DestroyBackBuffer(); + bgfx::frame(BGFX_FRAME_DISCARD); + } + + if (swapChain.nwh != nullptr) + { + if (bgfx::isValid(m_windowFrameBuffer)) + { + bgfx::updateSwapChain(m_windowFrameBuffer, swapChain); + } + else + { + m_windowFrameBuffer = bgfx::createFrameBuffer(swapChain); + if (!bgfx::isValid(m_windowFrameBuffer)) + { + throw std::runtime_error{"Failed to create the window frame buffer."}; + } + m_windowHandle = swapChain.nwh; + m_displayHandle = swapChain.ndt; + } + } + } + + bool DeviceImpl::RequestScreenShots() + { + bool requested = false; std::function)> callback; while (m_screenShotCallbacks.try_pop(callback, *m_cancellationSource)) { m_bgfxCallback.AddScreenShotCallback(std::move(callback)); -#if D3D12 - // D3D12 capture is immediate but needs an extra frame swap because back buffer is captured. - // Because of previous swapchain flip, back buffer is not what's just been rendered. - bgfx::frame(); -#endif - bgfx::requestScreenShot(BGFX_INVALID_HANDLE, "DeviceImpl::RequestScreenShot"); + requested = true; + } + { + std::scoped_lock lock{m_captureCallbacksMutex}; + if (!m_captureCallbacks.empty()) + { + m_bgfxCallback.CaptureNextScreenShot(); + requested = true; + } } + if (!requested) + { + return false; + } + if (!bgfx::isValid(GetBackBufferHandle())) + { + throw std::runtime_error{"Cannot capture without a window or an external back buffer."}; + } +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + if (bgfx::isValid(m_externalBackBuffer.FrameBuffer)) + { + return true; + } +#endif + // bgfx accepts only one screenshot per framebuffer in a frame. + bgfx::requestScreenShot(m_windowFrameBuffer, "DeviceImpl::RequestScreenShot"); + return false; } void DeviceImpl::Frame() @@ -724,12 +841,19 @@ namespace Babylon::Graphics UpdateBgfxState(); // Request screen shots before bgfx::frame. - RequestScreenShots(); + [[maybe_unused]] const bool externalScreenShot = RequestScreenShots(); // Advance frame and render! const uint8_t frameFlags = m_captureNextFrame.exchange(false) ? BGFX_FRAME_DEBUG_CAPTURE : 0; uint32_t frameNumber{bgfx::frame(frameFlags)}; +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + if (externalScreenShot) + { + ReadExternalBackBuffer(); + } +#endif + // Process read texture requests. while (!m_readTextureRequests.empty() && m_readTextureRequests.front().first <= frameNumber) { @@ -745,15 +869,6 @@ namespace Babylon::Graphics { std::scoped_lock callbackLock{m_captureCallbacksMutex}; - // If no one is listening anymore, stop capturing. - if (m_captureCallbacks.empty()) - { - std::scoped_lock stateLock{m_state.Mutex}; - m_state.Bgfx.Dirty = true; - m_state.Bgfx.InitState.resolution.reset &= ~BGFX_RESET_CAPTURE; - return; - } - for (const auto& callback : m_captureCallbacks) { callback(data); diff --git a/Core/Graphics/Source/DeviceImpl.h b/Core/Graphics/Source/DeviceImpl.h index 0f4edd96f7..455a94ac14 100644 --- a/Core/Graphics/Source/DeviceImpl.h +++ b/Core/Graphics/Source/DeviceImpl.h @@ -20,6 +20,10 @@ #include #include +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT +#include +#endif + namespace Babylon::Graphics { class DeviceImpl @@ -71,6 +75,7 @@ namespace Babylon::Graphics PlatformInfo GetPlatformInfo() const; uintptr_t GetId() const; + bgfx::FrameBufferHandle GetBackBufferHandle() const; /* ********** END DEVICE CONTRACT ********** */ @@ -130,18 +135,20 @@ namespace Babylon::Graphics friend class FrameCompletionScope; static const bgfx::RendererType::Enum s_bgfxRenderType; - void ConfigureBgfxPlatformData(bgfx::PlatformData& pd, WindowT window); - static void ConfigureBgfxRenderType(bgfx::PlatformData& pd, bgfx::RendererType::Enum& renderType); + void ConfigureBgfxSwapChain(bgfx::SwapChain& swapChain, WindowT window); + static void ConfigureBgfxRenderType(bgfx::Init& init); // Push the render resolution onto the native rendering surface so it // matches what bgfx renders into. Implemented per graphics API. The // window may be default-constructed (null) before UpdateWindow has run // (e.g. during construction), in which case there's nothing to size. - static void ResizeRenderSurface(WindowT window, uint32_t width, uint32_t height); + void ResizeRenderSurface(WindowT window, uint32_t width, uint32_t height); void UpdateBgfxState(); void UpdateBgfxResolution(); - void RequestScreenShots(); + void UpdateBackBufferState(); + void DestroyBackBuffer(); + bool RequestScreenShots(); void Frame(); void PerformMidFrameViewFlush(); void CaptureCallback(const BgfxCallback::CaptureData&); @@ -181,18 +188,43 @@ namespace Babylon::Graphics std::optional m_cancellationSource{}; + bgfx::FrameBufferHandle m_windowFrameBuffer{bgfx::kInvalidHandle}; + void* m_windowHandle{}; + void* m_displayHandle{}; +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + void CreateExternalBackBuffer(const bgfx::SwapChain& descriptor); + void DestroyExternalBackBuffer(); + void ReadExternalBackBuffer(); + + struct + { + winrt::com_ptr Color; + winrt::com_ptr Depth; + winrt::com_ptr ColorTexture; + winrt::com_ptr DepthTexture; + bgfx::FrameBufferHandle FrameBuffer{bgfx::kInvalidHandle}; + bgfx::TextureHandle ColorHandle{bgfx::kInvalidHandle}; + bgfx::TextureHandle DepthHandle{bgfx::kInvalidHandle}; + } m_externalBackBuffer; +#endif + struct { // Mutable since const getters need to lock. mutable std::recursive_mutex Mutex{}; // The native window/surface we render into. Cached as WindowT (the - // handle in Bgfx.InitState.platformData is type-erased to void* and + // handle in Bgfx.InitState.swapChain is type-erased to void* and // can't be cast back to WindowT portably) so ResizeRenderSurface can // push the render resolution onto the surface. Null until // UpdateWindow. WindowT Window{}; +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + winrt::com_ptr BackBufferColor; + winrt::com_ptr BackBufferDepthStencil; +#endif + struct { bgfx::Init InitState{}; diff --git a/Core/Graphics/Source/DeviceImpl_Android.cpp b/Core/Graphics/Source/DeviceImpl_Android.cpp index 9be4ac111d..a5d1f2686c 100644 --- a/Core/Graphics/Source/DeviceImpl_Android.cpp +++ b/Core/Graphics/Source/DeviceImpl_Android.cpp @@ -7,17 +7,17 @@ namespace Babylon::Graphics { - void DeviceImpl::ConfigureBgfxPlatformData(bgfx::PlatformData& pd, WindowT window) + void DeviceImpl::ConfigureBgfxSwapChain(bgfx::SwapChain& swapChain, WindowT window) { - pd.nwh = window; + swapChain.nwh = window; } - void DeviceImpl::ConfigureBgfxRenderType(bgfx::PlatformData& pd, bgfx::RendererType::Enum& renderType) + void DeviceImpl::ConfigureBgfxRenderType(bgfx::Init& init) { // on Android, having no window or context set the renderer API to no op. - if (!pd.nwh && !pd.context) + if (!init.swapChain.nwh && !init.platformData.context) { - renderType = bgfx::RendererType::Noop; + init.type = bgfx::RendererType::Noop; } } diff --git a/Core/Graphics/Source/DeviceImpl_D3D11.cpp b/Core/Graphics/Source/DeviceImpl_D3D11.cpp index ab94e57470..208369d218 100644 --- a/Core/Graphics/Source/DeviceImpl_D3D11.cpp +++ b/Core/Graphics/Source/DeviceImpl_D3D11.cpp @@ -1,6 +1,502 @@ #include #include "DeviceImpl.h" +#include +#include +#include + +#include +#include +#include +#include + +// clang-format off + +namespace Babylon::Graphics::D3D11TextureFormats +{ + // Copied from bgfx's renderer_d3d.h. These values are defined by newer Windows SDKs, but + // Babylon Native also supports SDKs where the corresponding DXGI_FORMAT names are absent. + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_4X4_UNORM = DXGI_FORMAT(134); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_4X4_UNORM_SRGB = DXGI_FORMAT(135); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_5X4_UNORM = DXGI_FORMAT(138); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_5X4_UNORM_SRGB = DXGI_FORMAT(139); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_5X5_UNORM = DXGI_FORMAT(142); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_5X5_UNORM_SRGB = DXGI_FORMAT(143); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_6X5_UNORM = DXGI_FORMAT(146); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_6X5_UNORM_SRGB = DXGI_FORMAT(147); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_6X6_UNORM = DXGI_FORMAT(150); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_6X6_UNORM_SRGB = DXGI_FORMAT(151); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_8X5_UNORM = DXGI_FORMAT(154); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_8X5_UNORM_SRGB = DXGI_FORMAT(155); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_8X6_UNORM = DXGI_FORMAT(158); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_8X6_UNORM_SRGB = DXGI_FORMAT(159); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_8X8_UNORM = DXGI_FORMAT(162); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_8X8_UNORM_SRGB = DXGI_FORMAT(163); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_10X5_UNORM = DXGI_FORMAT(166); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_10X5_UNORM_SRGB = DXGI_FORMAT(167); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_10X6_UNORM = DXGI_FORMAT(170); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_10X6_UNORM_SRGB = DXGI_FORMAT(171); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_10X8_UNORM = DXGI_FORMAT(174); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_10X8_UNORM_SRGB = DXGI_FORMAT(175); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_10X10_UNORM = DXGI_FORMAT(178); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_10X10_UNORM_SRGB = DXGI_FORMAT(179); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_12X10_UNORM = DXGI_FORMAT(182); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_12X10_UNORM_SRGB = DXGI_FORMAT(183); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_12X12_UNORM = DXGI_FORMAT(186); + inline constexpr DXGI_FORMAT DXGI_FORMAT_ASTC_12X12_UNORM_SRGB = DXGI_FORMAT(187); + + // Copied from renderer_d3d11.cpp + struct TextureFormatInfo + { + DXGI_FORMAT m_fmt; + DXGI_FORMAT m_fmtSrgb; + }; + + inline const TextureFormatInfo s_textureFormat[] = + { + { DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM_SRGB }, // BC1 + { DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM_SRGB }, // BC2 + { DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM_SRGB }, // BC3 + { DXGI_FORMAT_BC4_UNORM, DXGI_FORMAT_UNKNOWN }, // BC4 + { DXGI_FORMAT_BC4_SNORM, DXGI_FORMAT_UNKNOWN }, // BC4S + { DXGI_FORMAT_BC5_UNORM, DXGI_FORMAT_UNKNOWN }, // BC5 + { DXGI_FORMAT_BC5_SNORM, DXGI_FORMAT_UNKNOWN }, // BC5S + { DXGI_FORMAT_BC6H_SF16, DXGI_FORMAT_UNKNOWN }, // BC6H + { DXGI_FORMAT_BC6H_UF16, DXGI_FORMAT_UNKNOWN }, // BC6HU + { DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM_SRGB }, // BC7 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ETC1 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ETC2 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ETC2A + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ETC2A1 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // EACR11 UNORM + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // EACR11 SNORM + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // EACRG11 UNORM + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // EACRG11 SNORM + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC12 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC14 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC12A + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC14A + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC22 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC24 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ATC + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ATCE + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ATCI + { DXGI_FORMAT_ASTC_4X4_UNORM, DXGI_FORMAT_ASTC_4X4_UNORM_SRGB }, // ASTC4x4 + { DXGI_FORMAT_ASTC_5X4_UNORM, DXGI_FORMAT_ASTC_5X4_UNORM_SRGB }, // ASTC5x4 + { DXGI_FORMAT_ASTC_5X5_UNORM, DXGI_FORMAT_ASTC_5X5_UNORM_SRGB }, // ASTC5x5 + { DXGI_FORMAT_ASTC_6X5_UNORM, DXGI_FORMAT_ASTC_6X5_UNORM_SRGB }, // ASTC6x5 + { DXGI_FORMAT_ASTC_6X6_UNORM, DXGI_FORMAT_ASTC_6X6_UNORM_SRGB }, // ASTC6x6 + { DXGI_FORMAT_ASTC_8X5_UNORM, DXGI_FORMAT_ASTC_8X5_UNORM_SRGB }, // ASTC8x5 + { DXGI_FORMAT_ASTC_8X6_UNORM, DXGI_FORMAT_ASTC_8X6_UNORM_SRGB }, // ASTC8x6 + { DXGI_FORMAT_ASTC_8X8_UNORM, DXGI_FORMAT_ASTC_8X8_UNORM_SRGB }, // ASTC8x8 + { DXGI_FORMAT_ASTC_10X5_UNORM, DXGI_FORMAT_ASTC_10X5_UNORM_SRGB }, // ASTC10x5 + { DXGI_FORMAT_ASTC_10X6_UNORM, DXGI_FORMAT_ASTC_10X6_UNORM_SRGB }, // ASTC10x6 + { DXGI_FORMAT_ASTC_10X8_UNORM, DXGI_FORMAT_ASTC_10X8_UNORM_SRGB }, // ASTC10x8 + { DXGI_FORMAT_ASTC_10X10_UNORM, DXGI_FORMAT_ASTC_10X10_UNORM_SRGB}, // ASTC10x10 + { DXGI_FORMAT_ASTC_12X10_UNORM, DXGI_FORMAT_ASTC_12X10_UNORM_SRGB}, // ASTC12x10 + { DXGI_FORMAT_ASTC_12X12_UNORM, DXGI_FORMAT_ASTC_12X12_UNORM_SRGB}, // ASTC12x12 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // Unknown + { DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_UNKNOWN }, // R1 + { DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_UNKNOWN }, // A8 + { DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_UNKNOWN }, // R8 + { DXGI_FORMAT_R8_SINT, DXGI_FORMAT_UNKNOWN }, // R8I + { DXGI_FORMAT_R8_UINT, DXGI_FORMAT_UNKNOWN }, // R8U + { DXGI_FORMAT_R8_SNORM, DXGI_FORMAT_UNKNOWN }, // R8S + { DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_UNKNOWN }, // R16 + { DXGI_FORMAT_R16_SINT, DXGI_FORMAT_UNKNOWN }, // R16I + { DXGI_FORMAT_R16_UINT, DXGI_FORMAT_UNKNOWN }, // R16U + { DXGI_FORMAT_R16_FLOAT, DXGI_FORMAT_UNKNOWN }, // R16F + { DXGI_FORMAT_R16_SNORM, DXGI_FORMAT_UNKNOWN }, // R16S + { DXGI_FORMAT_R32_SINT, DXGI_FORMAT_UNKNOWN }, // R32I + { DXGI_FORMAT_R32_UINT, DXGI_FORMAT_UNKNOWN }, // R32U + { DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_UNKNOWN }, // R32F + { DXGI_FORMAT_R8G8_UNORM, DXGI_FORMAT_UNKNOWN }, // RG8 + { DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_UNKNOWN }, // RG8I + { DXGI_FORMAT_R8G8_UINT, DXGI_FORMAT_UNKNOWN }, // RG8U + { DXGI_FORMAT_R8G8_SNORM, DXGI_FORMAT_UNKNOWN }, // RG8S + { DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_UNKNOWN }, // RG16 + { DXGI_FORMAT_R16G16_SINT, DXGI_FORMAT_UNKNOWN }, // RG16I + { DXGI_FORMAT_R16G16_UINT, DXGI_FORMAT_UNKNOWN }, // RG16U + { DXGI_FORMAT_R16G16_FLOAT, DXGI_FORMAT_UNKNOWN }, // RG16F + { DXGI_FORMAT_R16G16_SNORM, DXGI_FORMAT_UNKNOWN }, // RG16S + { DXGI_FORMAT_R32G32_SINT, DXGI_FORMAT_UNKNOWN }, // RG32I + { DXGI_FORMAT_R32G32_UINT, DXGI_FORMAT_UNKNOWN }, // RG32U + { DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_UNKNOWN }, // RG32F + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // RGB8 + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // RGB8I + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // RGB8U + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // RGB8S + { DXGI_FORMAT_R9G9B9E5_SHAREDEXP, DXGI_FORMAT_UNKNOWN }, // RGB9E5F + { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB }, // BGRA8 + { DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB }, // RGBA8 + { DXGI_FORMAT_R8G8B8A8_SINT, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB }, // RGBA8I + { DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB }, // RGBA8U + { DXGI_FORMAT_R8G8B8A8_SNORM, DXGI_FORMAT_UNKNOWN }, // RGBA8S + { DXGI_FORMAT_R16G16B16A16_UNORM, DXGI_FORMAT_UNKNOWN }, // RGBA16 + { DXGI_FORMAT_R16G16B16A16_SINT, DXGI_FORMAT_UNKNOWN }, // RGBA16I + { DXGI_FORMAT_R16G16B16A16_UINT, DXGI_FORMAT_UNKNOWN }, // RGBA16U + { DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_UNKNOWN }, // RGBA16F + { DXGI_FORMAT_R16G16B16A16_SNORM, DXGI_FORMAT_UNKNOWN }, // RGBA16S + { DXGI_FORMAT_R32G32B32A32_SINT, DXGI_FORMAT_UNKNOWN }, // RGBA32I + { DXGI_FORMAT_R32G32B32A32_UINT, DXGI_FORMAT_UNKNOWN }, // RGBA32U + { DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_UNKNOWN }, // RGBA32F + { DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_UNKNOWN }, // B5G6R5 + { DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_UNKNOWN }, // R5G6B5 + { DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_UNKNOWN }, // BGRA4 + { DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_UNKNOWN }, // RGBA4 + { DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_UNKNOWN }, // BGR5A1 + { DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB5A1 + { DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB10A2 + { DXGI_FORMAT_R10G10B10A2_UINT, DXGI_FORMAT_UNKNOWN }, // RGB10A2U + { DXGI_FORMAT_R11G11B10_FLOAT, DXGI_FORMAT_UNKNOWN }, // RG11B10F + { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // UnknownDepth + { DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D16 + { DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D24 + { DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D24S8 + { DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D32 + { DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D16F + { DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D24F + { DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D32F + { DXGI_FORMAT_R32G8X24_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D32FS8 + { DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D0S8 + }; + static_assert(bgfx::TextureFormat::Count == BX_COUNTOF(s_textureFormat)); + + std::optional TryGetBgfxTextureFormat(DXGI_FORMAT format) + { + if (format == DXGI_FORMAT_UNKNOWN) + { + return std::nullopt; + } + + for (int i = 0; i < BX_COUNTOF(s_textureFormat); ++i) + { + if (s_textureFormat[i].m_fmt == format) + { + return BgfxTextureFormat{static_cast(i), false}; + } + if (s_textureFormat[i].m_fmtSrgb == format) + { + return BgfxTextureFormat{static_cast(i), true}; + } + } + + return std::nullopt; + } + + std::optional TryGetBgfxDepthFormat(DXGI_FORMAT format) + { + switch (format) + { + case DXGI_FORMAT_D16_UNORM: + return bgfx::TextureFormat::D16; + case DXGI_FORMAT_D24_UNORM_S8_UINT: + return bgfx::TextureFormat::D24S8; + case DXGI_FORMAT_D32_FLOAT: + return bgfx::TextureFormat::D32F; + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + return bgfx::TextureFormat::D32FS8; + default: + return std::nullopt; + } + } +} + +// clang-format on + +namespace +{ + void ThrowIfFailed(HRESULT result, const char* message) + { + if (FAILED(result)) + { + throw std::runtime_error{message}; + } + } + + uint64_t GetMsaaFlags(uint32_t sampleCount) + { + switch (sampleCount) + { + case 1: + return BGFX_TEXTURE_NONE; + case 2: + return BGFX_TEXTURE_RT_MSAA_X2; + case 4: + return BGFX_TEXTURE_RT_MSAA_X4; + case 8: + return BGFX_TEXTURE_RT_MSAA_X8; + case 16: + return BGFX_TEXTURE_RT_MSAA_X16; + default: + throw std::runtime_error{"Unsupported D3D11 external back buffer sample count."}; + } + } + + struct ViewInfo + { + winrt::com_ptr Texture; + D3D11_TEXTURE2D_DESC TextureDesc{}; + DXGI_FORMAT ViewFormat{DXGI_FORMAT_UNKNOWN}; + uint32_t Mip{}; + uint32_t FirstLayer{}; + uint32_t NumLayers{1}; + uint32_t Width{}; + uint32_t Height{}; + uint8_t AttachmentFlags{BGFX_ATTACHMENT_NONE}; + bgfx::TextureFormat::Enum BgfxFormat{bgfx::TextureFormat::Unknown}; + bool Srgb{}; + }; + + ViewInfo GetTextureInfo(ID3D11View* view) + { + winrt::com_ptr resource; + view->GetResource(resource.put()); + + D3D11_RESOURCE_DIMENSION dimension{}; + resource->GetType(&dimension); + if (dimension != D3D11_RESOURCE_DIMENSION_TEXTURE2D) + { + throw std::runtime_error{"D3D11 external back buffers must reference a Texture2D resource."}; + } + + ViewInfo info{}; + info.Texture = resource.as(); + info.Texture->GetDesc(&info.TextureDesc); + return info; + } + + void SetViewDimensions(ViewInfo& info) + { + info.Width = std::max(1u, info.TextureDesc.Width >> info.Mip); + info.Height = std::max(1u, info.TextureDesc.Height >> info.Mip); + if (info.FirstLayer + info.NumLayers > info.TextureDesc.ArraySize) + { + throw std::runtime_error{"D3D11 external back buffer view exceeds its texture array."}; + } + } + + ViewInfo GetColorInfo(ID3D11RenderTargetView* view) + { + ViewInfo info = GetTextureInfo(view); + + D3D11_RENDER_TARGET_VIEW_DESC desc{}; + view->GetDesc(&desc); + info.ViewFormat = desc.Format; + switch (desc.ViewDimension) + { + case D3D11_RTV_DIMENSION_TEXTURE2D: + info.Mip = desc.Texture2D.MipSlice; + break; + case D3D11_RTV_DIMENSION_TEXTURE2DARRAY: + info.Mip = desc.Texture2DArray.MipSlice; + info.FirstLayer = desc.Texture2DArray.FirstArraySlice; + info.NumLayers = desc.Texture2DArray.ArraySize; + break; + case D3D11_RTV_DIMENSION_TEXTURE2DMS: + break; + case D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY: + info.FirstLayer = desc.Texture2DMSArray.FirstArraySlice; + info.NumLayers = desc.Texture2DMSArray.ArraySize; + break; + default: + throw std::runtime_error{"Unsupported D3D11 external color back buffer view dimension."}; + } + + const auto format = Babylon::Graphics::D3D11TextureFormats::TryGetBgfxTextureFormat(info.ViewFormat); + if (!format || format->Format == bgfx::TextureFormat::Unknown) + { + throw std::runtime_error{"Unsupported D3D11 external color back buffer format."}; + } + info.BgfxFormat = format->Format; + info.Srgb = format->Srgb; + SetViewDimensions(info); + return info; + } + + ViewInfo GetDepthInfo(ID3D11DepthStencilView* view) + { + ViewInfo info = GetTextureInfo(view); + + D3D11_DEPTH_STENCIL_VIEW_DESC desc{}; + view->GetDesc(&desc); + info.ViewFormat = desc.Format; + switch (desc.ViewDimension) + { + case D3D11_DSV_DIMENSION_TEXTURE2D: + info.Mip = desc.Texture2D.MipSlice; + break; + case D3D11_DSV_DIMENSION_TEXTURE2DARRAY: + info.Mip = desc.Texture2DArray.MipSlice; + info.FirstLayer = desc.Texture2DArray.FirstArraySlice; + info.NumLayers = desc.Texture2DArray.ArraySize; + break; + case D3D11_DSV_DIMENSION_TEXTURE2DMS: + break; + case D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY: + info.FirstLayer = desc.Texture2DMSArray.FirstArraySlice; + info.NumLayers = desc.Texture2DMSArray.ArraySize; + break; + default: + throw std::runtime_error{"Unsupported D3D11 external depth back buffer view dimension."}; + } + + const auto format = Babylon::Graphics::D3D11TextureFormats::TryGetBgfxDepthFormat(info.ViewFormat); + if (!format) + { + throw std::runtime_error{"Unsupported D3D11 external depth back buffer format."}; + } + info.BgfxFormat = *format; + info.AttachmentFlags = static_cast( + ((desc.Flags & D3D11_DSV_READ_ONLY_DEPTH) ? BGFX_ATTACHMENT_READ_ONLY_DEPTH : 0) | + ((desc.Flags & D3D11_DSV_READ_ONLY_STENCIL) ? BGFX_ATTACHMENT_READ_ONLY_STENCIL : 0)); + SetViewDimensions(info); + return info; + } + + void ValidateDevice(const ViewInfo& info, ID3D11Device* bgfxDevice) + { + winrt::com_ptr resourceDevice; + info.Texture->GetDevice(resourceDevice.put()); + const auto resourceIdentity = resourceDevice.as(); + + winrt::com_ptr retainedBgfxDevice; + retainedBgfxDevice.copy_from(bgfxDevice); + const auto bgfxIdentity = retainedBgfxDevice.as(); + if (resourceIdentity.get() != bgfxIdentity.get()) + { + throw std::runtime_error{"D3D11 external back buffer belongs to a different device."}; + } + } + + bgfx::TextureHandle ImportTexture(const ViewInfo& info) + { + uint64_t flags = BGFX_TEXTURE_RT_WRITE_ONLY | GetMsaaFlags(info.TextureDesc.SampleDesc.Count); + if (info.Srgb) + { + flags |= BGFX_TEXTURE_SRGB; + } + + const auto handle = bgfx::createTexture2D( + static_cast(info.TextureDesc.Width), + static_cast(info.TextureDesc.Height), + info.TextureDesc.MipLevels > 1, + static_cast(info.TextureDesc.ArraySize), + info.BgfxFormat, + flags, + nullptr, + static_cast(reinterpret_cast(info.Texture.get()))); + if (!bgfx::isValid(handle)) + { + throw std::runtime_error{"Failed to import D3D11 external back buffer texture."}; + } + return handle; + } + + bgfx::Attachment MakeAttachment(bgfx::TextureHandle handle, const ViewInfo& info) + { + bgfx::Attachment attachment{}; + attachment.init( + handle, + bgfx::Access::Write, + static_cast(info.FirstLayer), + static_cast(info.NumLayers), + static_cast(info.Mip), + info.AttachmentFlags); + return attachment; + } + + bgfx::TextureFormat::Enum GetCaptureFormat(DXGI_FORMAT format) + { + switch (format) + { + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + return bgfx::TextureFormat::BGRA8; + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return bgfx::TextureFormat::RGBA8; + default: + throw std::runtime_error{"Unsupported D3D11 external back buffer capture format."}; + } + } + + struct DepthFormats + { + DXGI_FORMAT Resource; + DXGI_FORMAT View; + }; + + DepthFormats GetDepthFormats(bgfx::TextureFormat::Enum format) + { + switch (format) + { + case bgfx::TextureFormat::D16: + return {DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_D16_UNORM}; + case bgfx::TextureFormat::D24S8: + return {DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_D24_UNORM_S8_UINT}; + case bgfx::TextureFormat::D32F: + return {DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_D32_FLOAT}; + case bgfx::TextureFormat::D32FS8: + return {DXGI_FORMAT_R32G8X24_TYPELESS, DXGI_FORMAT_D32_FLOAT_S8X24_UINT}; + default: + throw std::runtime_error{"Unsupported generated D3D11 back buffer depth format."}; + } + } + + ViewInfo CreateDepthInfo( + ID3D11Device* device, + const ViewInfo& colorInfo, + bgfx::TextureFormat::Enum format, + winrt::com_ptr& texture, + winrt::com_ptr& view) + { + const DepthFormats formats = GetDepthFormats(format); + + D3D11_TEXTURE2D_DESC textureDesc{}; + textureDesc.Width = colorInfo.Width; + textureDesc.Height = colorInfo.Height; + textureDesc.MipLevels = 1; + textureDesc.ArraySize = colorInfo.NumLayers; + textureDesc.Format = formats.Resource; + textureDesc.SampleDesc = colorInfo.TextureDesc.SampleDesc; + textureDesc.Usage = D3D11_USAGE_DEFAULT; + textureDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + ThrowIfFailed( + device->CreateTexture2D(&textureDesc, nullptr, texture.put()), + "Failed to create native depth texture for D3D11 external back buffer."); + + D3D11_DEPTH_STENCIL_VIEW_DESC viewDesc{}; + viewDesc.Format = formats.View; + if (textureDesc.SampleDesc.Count > 1) + { + if (textureDesc.ArraySize > 1) + { + viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY; + viewDesc.Texture2DMSArray.ArraySize = textureDesc.ArraySize; + } + else + { + viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS; + } + } + else if (textureDesc.ArraySize > 1) + { + viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY; + viewDesc.Texture2DArray.ArraySize = textureDesc.ArraySize; + } + else + { + viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + } + + ThrowIfFailed( + device->CreateDepthStencilView(texture.get(), &viewDesc, view.put()), + "Failed to create native depth view for D3D11 external back buffer."); + return GetDepthInfo(view.get()); + } +} + namespace Babylon::Graphics { const bgfx::RendererType::Enum DeviceImpl::s_bgfxRenderType = bgfx::RendererType::Direct3D11; @@ -14,4 +510,202 @@ namespace Babylon::Graphics { // No-op: the swap chain size is managed elsewhere on this platform. } + + void DeviceImpl::CreateExternalBackBuffer(const bgfx::SwapChain& descriptor) + { + auto* color = m_state.BackBufferColor.get(); + auto* depth = m_state.BackBufferDepthStencil.get(); + const auto* internalData = bgfx::getInternalData(); + auto* bgfxDevice = internalData == nullptr ? nullptr : static_cast(internalData->context); + if (bgfxDevice == nullptr) + { + throw std::runtime_error{"D3D11 device is unavailable while importing an external back buffer."}; + } + + bool cleanupNeeded = true; + const auto cleanup = gsl::finally([this, &cleanupNeeded] { + if (cleanupNeeded) + { + DestroyExternalBackBuffer(); + } + }); + + std::optional colorInfo; + if (color != nullptr) + { + m_externalBackBuffer.Color.copy_from(color); + colorInfo = GetColorInfo(color); + ValidateDevice(*colorInfo, bgfxDevice); + m_externalBackBuffer.ColorTexture = colorInfo->Texture; + m_externalBackBuffer.ColorHandle = ImportTexture(*colorInfo); + } + + std::optional depthInfo; + if (depth != nullptr) + { + m_externalBackBuffer.Depth.copy_from(depth); + depthInfo = GetDepthInfo(depth); + ValidateDevice(*depthInfo, bgfxDevice); + if (color == nullptr && descriptor.nwh != nullptr) + { + D3D11_DEPTH_STENCIL_VIEW_DESC viewDesc{}; + depth->GetDesc(&viewDesc); + // SwapChain::depth cannot carry a mip, array range or read-only flags. + if ((viewDesc.ViewDimension != D3D11_DSV_DIMENSION_TEXTURE2D && + viewDesc.ViewDimension != D3D11_DSV_DIMENSION_TEXTURE2DMS) || + depthInfo->Mip != 0 || depthInfo->TextureDesc.ArraySize != 1 || viewDesc.Flags != 0) + { + throw std::runtime_error{ + "A D3D11 window back buffer requires a writable, non-array, mip-0 depth view when no color view is supplied."}; + } + } + m_externalBackBuffer.DepthTexture = depthInfo->Texture; + m_externalBackBuffer.DepthHandle = ImportTexture(*depthInfo); + } + + if (colorInfo && depthInfo && + (colorInfo->Width != depthInfo->Width || + colorInfo->Height != depthInfo->Height || + colorInfo->NumLayers != depthInfo->NumLayers || + colorInfo->TextureDesc.SampleDesc.Count != depthInfo->TextureDesc.SampleDesc.Count || + colorInfo->TextureDesc.SampleDesc.Quality != depthInfo->TextureDesc.SampleDesc.Quality)) + { + throw std::runtime_error{"D3D11 external color and depth back buffer views do not match."}; + } + + if (colorInfo && !depthInfo && descriptor.formatDepthStencil != bgfx::TextureFormat::Count) + { + depthInfo = CreateDepthInfo( + bgfxDevice, + *colorInfo, + descriptor.formatDepthStencil, + m_externalBackBuffer.DepthTexture, + m_externalBackBuffer.Depth); + m_externalBackBuffer.DepthHandle = ImportTexture(*depthInfo); + } + + std::array attachments{}; + uint8_t attachmentCount{}; + if (colorInfo) + { + attachments[attachmentCount++] = MakeAttachment(m_externalBackBuffer.ColorHandle, *colorInfo); + } + if (depthInfo && (colorInfo || descriptor.nwh == nullptr)) + { + attachments[attachmentCount++] = MakeAttachment(m_externalBackBuffer.DepthHandle, *depthInfo); + } + + if (attachmentCount != 0) + { + m_externalBackBuffer.FrameBuffer = bgfx::createFrameBuffer(attachmentCount, attachments.data(), false); + if (!bgfx::isValid(m_externalBackBuffer.FrameBuffer)) + { + throw std::runtime_error{"Failed to create D3D11 external back buffer framebuffer."}; + } + } + + cleanupNeeded = false; + } + + void DeviceImpl::ReadExternalBackBuffer() + { + if (!m_externalBackBuffer.Color || !m_externalBackBuffer.ColorTexture) + { + throw std::runtime_error{"Cannot capture a D3D11 external back buffer without a color view."}; + } + + const auto colorInfo = GetColorInfo(m_externalBackBuffer.Color.get()); + const bgfx::TextureFormat::Enum captureFormat = GetCaptureFormat(colorInfo.ViewFormat); + + winrt::com_ptr device; + m_externalBackBuffer.ColorTexture->GetDevice(device.put()); + winrt::com_ptr context; + device->GetImmediateContext(context.put()); + + D3D11_TEXTURE2D_DESC copyDesc{}; + copyDesc.Width = colorInfo.Width; + copyDesc.Height = colorInfo.Height; + copyDesc.MipLevels = 1; + copyDesc.ArraySize = 1; + copyDesc.Format = colorInfo.ViewFormat; + copyDesc.SampleDesc.Count = 1; + copyDesc.Usage = D3D11_USAGE_STAGING; + copyDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + winrt::com_ptr staging; + ThrowIfFailed( + device->CreateTexture2D(©Desc, nullptr, staging.put()), + "Failed to create staging texture for D3D11 external back buffer capture."); + + const uint32_t sourceSubresource = D3D11CalcSubresource( + colorInfo.Mip, + colorInfo.FirstLayer, + colorInfo.TextureDesc.MipLevels); + + if (colorInfo.TextureDesc.SampleDesc.Count > 1) + { + D3D11_TEXTURE2D_DESC resolveDesc = copyDesc; + resolveDesc.Usage = D3D11_USAGE_DEFAULT; + resolveDesc.CPUAccessFlags = 0; + + winrt::com_ptr resolved; + ThrowIfFailed( + device->CreateTexture2D(&resolveDesc, nullptr, resolved.put()), + "Failed to create resolve texture for D3D11 external back buffer capture."); + context->ResolveSubresource( + resolved.get(), + 0, + m_externalBackBuffer.ColorTexture.get(), + sourceSubresource, + colorInfo.ViewFormat); + context->CopyResource(staging.get(), resolved.get()); + } + else + { + context->CopySubresourceRegion( + staging.get(), + 0, + 0, + 0, + 0, + m_externalBackBuffer.ColorTexture.get(), + sourceSubresource, + nullptr); + } + + D3D11_MAPPED_SUBRESOURCE mapped{}; + ThrowIfFailed( + context->Map(staging.get(), 0, D3D11_MAP_READ, 0, &mapped), + "Failed to map D3D11 external back buffer capture."); + const auto unmap = gsl::finally([&] { context->Unmap(staging.get(), 0); }); + + m_bgfxCallback.CompleteScreenShot(BgfxCallback::CaptureData{ + colorInfo.Width, + colorInfo.Height, + mapped.RowPitch, + captureFormat, + false, + mapped.pData, + mapped.RowPitch * colorInfo.Height}); + } + + void DeviceImpl::DestroyExternalBackBuffer() + { + if (bgfx::isValid(m_externalBackBuffer.FrameBuffer)) + { + bgfx::destroy(m_externalBackBuffer.FrameBuffer); + m_externalBackBuffer.FrameBuffer = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(m_externalBackBuffer.ColorHandle)) + { + bgfx::destroy(m_externalBackBuffer.ColorHandle); + m_externalBackBuffer.ColorHandle = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(m_externalBackBuffer.DepthHandle)) + { + bgfx::destroy(m_externalBackBuffer.DepthHandle); + m_externalBackBuffer.DepthHandle = BGFX_INVALID_HANDLE; + } + m_externalBackBuffer = {}; + } } diff --git a/Core/Graphics/Source/DeviceImpl_Unix.cpp b/Core/Graphics/Source/DeviceImpl_Unix.cpp index 2de4f45829..993a74c536 100644 --- a/Core/Graphics/Source/DeviceImpl_Unix.cpp +++ b/Core/Graphics/Source/DeviceImpl_Unix.cpp @@ -10,7 +10,7 @@ namespace Babylon::Graphics { - void DeviceImpl::ConfigureBgfxPlatformData(bgfx::PlatformData& pd, WindowT window) + void DeviceImpl::ConfigureBgfxSwapChain(bgfx::SwapChain& swapChain, WindowT window) { if (s_bgfxRenderType == bgfx::RendererType::Vulkan && window != WindowT{} && !m_nativeDisplay) { @@ -23,11 +23,11 @@ namespace Babylon::Graphics XCloseDisplay(static_cast(value)); }}; } - pd.ndt = m_nativeDisplay.get(); - pd.nwh = reinterpret_cast(window); + swapChain.ndt = m_nativeDisplay.get(); + swapChain.nwh = reinterpret_cast(window); } - void DeviceImpl::ConfigureBgfxRenderType(bgfx::PlatformData& /*pd*/, bgfx::RendererType::Enum& /*renderType*/) + void DeviceImpl::ConfigureBgfxRenderType(bgfx::Init& /*init*/) { } diff --git a/Core/Graphics/Source/DeviceImpl_Vulkan.cpp b/Core/Graphics/Source/DeviceImpl_Vulkan.cpp index 1ce2d91c29..48071f56a7 100644 --- a/Core/Graphics/Source/DeviceImpl_Vulkan.cpp +++ b/Core/Graphics/Source/DeviceImpl_Vulkan.cpp @@ -10,8 +10,20 @@ namespace Babylon::Graphics return {static_cast(bgfx::getInternalData()->context)}; } - void DeviceImpl::ResizeRenderSurface(WindowT /*window*/, uint32_t /*width*/, uint32_t /*height*/) + void DeviceImpl::ResizeRenderSurface(WindowT window, uint32_t width, uint32_t height) { - // No-op: the surface size is managed elsewhere on this platform. +#if defined(__linux__) && !defined(__ANDROID__) + if (window != WindowT{} && width != 0 && height != 0 && m_nativeDisplay) + { + auto* display = static_cast(m_nativeDisplay.get()); + XResizeWindow(display, window, width, height); + // Vulkan uses the X11 surface's current extent, so apply the resize before updating the swap chain. + XSync(display, False); + } +#else + (void)window; + (void)width; + (void)height; +#endif } } diff --git a/Core/Graphics/Source/DeviceImpl_Win32.cpp b/Core/Graphics/Source/DeviceImpl_Win32.cpp index 63e6f0c33f..b09781e901 100644 --- a/Core/Graphics/Source/DeviceImpl_Win32.cpp +++ b/Core/Graphics/Source/DeviceImpl_Win32.cpp @@ -5,12 +5,12 @@ namespace Babylon::Graphics { - void DeviceImpl::ConfigureBgfxPlatformData(bgfx::PlatformData& pd, WindowT window) + void DeviceImpl::ConfigureBgfxSwapChain(bgfx::SwapChain& swapChain, WindowT window) { - pd.nwh = window; + swapChain.nwh = window; } - void DeviceImpl::ConfigureBgfxRenderType(bgfx::PlatformData& /*pd*/, bgfx::RendererType::Enum& /*renderType*/) + void DeviceImpl::ConfigureBgfxRenderType(bgfx::Init& /*init*/) { } diff --git a/Core/Graphics/Source/DeviceImpl_WinRT.cpp b/Core/Graphics/Source/DeviceImpl_WinRT.cpp index e91bb6f5c1..a8f1e6472d 100644 --- a/Core/Graphics/Source/DeviceImpl_WinRT.cpp +++ b/Core/Graphics/Source/DeviceImpl_WinRT.cpp @@ -8,20 +8,21 @@ namespace Babylon::Graphics { - void DeviceImpl::ConfigureBgfxPlatformData(bgfx::PlatformData& pd, WindowT window) + void DeviceImpl::ConfigureBgfxSwapChain(bgfx::SwapChain& swapChain, WindowT window) { + swapChain.ndt = nullptr; // Assume window is a xaml swap chain panel if not a core window. if (!window.try_as()) { // Set ndt greater than 1 for xaml swap chain panels. // See https://github.com/bkaradzic/bgfx/blob/23edb9c4d90744bf90a89ff9e7308b8ff6517fee/src/dxgi.cpp#L531-L552 - pd.ndt = reinterpret_cast(2); + swapChain.ndt = reinterpret_cast(2); } - pd.nwh = winrt::get_abi(window); + swapChain.nwh = winrt::get_abi(window); } - void DeviceImpl::ConfigureBgfxRenderType(bgfx::PlatformData& /*pd*/, bgfx::RendererType::Enum& /*renderType*/) + void DeviceImpl::ConfigureBgfxRenderType(bgfx::Init& /*init*/) { } diff --git a/Core/Graphics/Source/DeviceImpl_iOS.mm b/Core/Graphics/Source/DeviceImpl_iOS.mm index 8c1f900a55..cb9dd61d7c 100644 --- a/Core/Graphics/Source/DeviceImpl_iOS.mm +++ b/Core/Graphics/Source/DeviceImpl_iOS.mm @@ -17,12 +17,12 @@ bool IsValidScale(float scale) namespace Babylon::Graphics { - void DeviceImpl::ConfigureBgfxPlatformData(bgfx::PlatformData& pd, WindowT window) + void DeviceImpl::ConfigureBgfxSwapChain(bgfx::SwapChain& swapChain, WindowT window) { - pd.nwh = window; + swapChain.nwh = window; } - void DeviceImpl::ConfigureBgfxRenderType(bgfx::PlatformData& /*pd*/, bgfx::RendererType::Enum& /*renderType*/) + void DeviceImpl::ConfigureBgfxRenderType(bgfx::Init& /*init*/) { } diff --git a/Core/Graphics/Source/DeviceImpl_macOS.mm b/Core/Graphics/Source/DeviceImpl_macOS.mm index 4027c7cb9c..6d0e91a881 100644 --- a/Core/Graphics/Source/DeviceImpl_macOS.mm +++ b/Core/Graphics/Source/DeviceImpl_macOS.mm @@ -8,12 +8,12 @@ namespace Babylon::Graphics { - void DeviceImpl::ConfigureBgfxPlatformData(bgfx::PlatformData& pd, WindowT window) + void DeviceImpl::ConfigureBgfxSwapChain(bgfx::SwapChain& swapChain, WindowT window) { - pd.nwh = window; + swapChain.nwh = window; } - void DeviceImpl::ConfigureBgfxRenderType(bgfx::PlatformData& /*pd*/, bgfx::RendererType::Enum& /*renderType*/) + void DeviceImpl::ConfigureBgfxRenderType(bgfx::Init& /*init*/) { } diff --git a/Core/Graphics/Source/DeviceImpl_visionOS.mm b/Core/Graphics/Source/DeviceImpl_visionOS.mm index 1fb5c3dd3e..bef49f0861 100644 --- a/Core/Graphics/Source/DeviceImpl_visionOS.mm +++ b/Core/Graphics/Source/DeviceImpl_visionOS.mm @@ -4,12 +4,12 @@ namespace Babylon::Graphics { - void DeviceImpl::ConfigureBgfxPlatformData(bgfx::PlatformData& pd, WindowT window) + void DeviceImpl::ConfigureBgfxSwapChain(bgfx::SwapChain& swapChain, WindowT window) { - pd.nwh = window; + swapChain.nwh = window; } - void DeviceImpl::ConfigureBgfxRenderType(bgfx::PlatformData& /*pd*/, bgfx::RendererType::Enum& /*renderType*/) + void DeviceImpl::ConfigureBgfxRenderType(bgfx::Init& /*init*/) { } diff --git a/Core/Graphics/Source/FrameBuffer.cpp b/Core/Graphics/Source/FrameBuffer.cpp index e2af51a3d4..d3c2796db2 100644 --- a/Core/Graphics/Source/FrameBuffer.cpp +++ b/Core/Graphics/Source/FrameBuffer.cpp @@ -12,6 +12,8 @@ namespace Babylon::Graphics , m_width{width} , m_height{height} , m_defaultBackBuffer{defaultBackBuffer} + // XR uses default framebuffer semantics but supplies an explicit render target. + , m_useDeviceBackBuffer{defaultBackBuffer && !bgfx::isValid(handle)} , m_hasDepth{hasDepth} , m_hasStencil{hasStencil} , m_disposed{false} @@ -51,7 +53,7 @@ namespace Babylon::Graphics bgfx::FrameBufferHandle FrameBuffer::Handle() const { - return m_handle; + return m_useDeviceBackBuffer ? m_deviceContext.GetBackBufferHandle() : m_handle; } uint16_t FrameBuffer::Width() const @@ -86,7 +88,7 @@ namespace Babylon::Graphics bgfx::setViewMode(m_viewId.value(), bgfx::ViewMode::Sequential); bgfx::setViewClear(m_viewId.value(), flags, rgba, depth, stencil); - bgfx::setViewFrameBuffer(m_viewId.value(), m_handle); + bgfx::setViewFrameBuffer(m_viewId.value(), Handle()); // If a scissor is not set, WebGL clears the entire screen, so set the view rect to cover the entire screen // before clearing to match WebGL's behavior; otherwise BGFX will only clear the view rect. @@ -222,7 +224,7 @@ namespace Babylon::Graphics bgfx::setViewMode(m_viewId.value(), bgfx::ViewMode::Sequential); bgfx::setViewClear(m_viewId.value(), BGFX_CLEAR_NONE, 0, 1.0f, 0); - bgfx::setViewFrameBuffer(m_viewId.value(), m_handle); + bgfx::setViewFrameBuffer(m_viewId.value(), Handle()); m_bgfxViewPort = viewPort; bgfx::setViewRect(m_viewId.value(), diff --git a/Core/Graphics/Source/Texture.cpp b/Core/Graphics/Source/Texture.cpp index 69a0321c09..c3aff540e1 100644 --- a/Core/Graphics/Source/Texture.cpp +++ b/Core/Graphics/Source/Texture.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace { @@ -36,6 +37,15 @@ namespace Babylon::Graphics m_handle = BGFX_INVALID_HANDLE; m_ownsHandle = false; } + + if (m_nativeTextureOwner) + { + // Cross a full render boundary, even when Dispose is called from AfterRender. + // Only the native owner is deferred; the Texture wrapper is never accessed. + arcana::make_task(m_deviceContext.BeforeRenderScheduler(), arcana::cancellation::none(), [] {}) + .then(m_deviceContext.AfterRenderScheduler(), arcana::cancellation::none(), + [owner = std::move(m_nativeTextureOwner)] { (void)owner; }); + } } bool Texture::IsValid() const @@ -65,7 +75,7 @@ namespace Babylon::Graphics m_flags = flags; } - void Texture::Create2D(uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags, uintptr_t nativeTextureHandle) + void Texture::Create2D(uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags, uintptr_t nativeTextureHandle, std::shared_ptr nativeTextureOwner) { Dispose(); @@ -82,6 +92,7 @@ namespace Babylon::Graphics } m_ownsHandle = true; + m_nativeTextureOwner = std::move(nativeTextureOwner); SetMetadata(width, height, 0, hasMips, false, false, numLayers, format, flags); } diff --git a/Dependencies/CMakeLists.txt b/Dependencies/CMakeLists.txt index b3817d9fe7..a6cdae9531 100644 --- a/Dependencies/CMakeLists.txt +++ b/Dependencies/CMakeLists.txt @@ -37,6 +37,7 @@ set(BGFX_CUSTOM_TARGETS OFF) set(BGFX_INSTALL OFF) set(BGFX_OPENGL_USE_EGL ON) set(BGFX_USE_DEBUG_SUFFIX OFF) +set(BGFX_CONFIG_VIDEO OFF) # Keep AVIF opt-in, including caches with bgfx.cmake's empty (inherit) default. if(NOT DEFINED BIMG_CONFIG_PARSE_AVIF OR "${BIMG_CONFIG_PARSE_AVIF}" STREQUAL "") @@ -72,9 +73,6 @@ target_compile_definitions(bgfx PRIVATE BGFX_CONFIG_MAX_FRAME_BUFFERS=${BGFX_CON # Temporary disable uniform debug. target_compile_definitions(bgfx PRIVATE BGFX_CONFIG_DEBUG_UNIFORM=0) -# Disable video decoding support (not used by Babylon Native). -target_compile_definitions(bgfx PRIVATE BGFX_CONFIG_VIDEO=0) - # Disable the C99 API (Babylon Native uses the C++ API only); saves binary size. target_compile_definitions(bgfx PRIVATE BGFX_CONFIG_C99_API=0) @@ -94,7 +92,6 @@ endif() set_property(TARGET bimg PROPERTY FOLDER Dependencies/bgfx/3rdparty) set_property(TARGET bimg_encode PROPERTY FOLDER Dependencies/bgfx/3rdparty) set_property(TARGET bimg_decode PROPERTY FOLDER Dependencies/bgfx/3rdparty) -set_property(TARGET minz PROPERTY FOLDER Dependencies/bgfx/3rdparty) if(TARGET tinyexr) set_property(TARGET tinyexr PROPERTY FOLDER Dependencies/bgfx/3rdparty) endif() diff --git a/Documentation/Components.md b/Documentation/Components.md index f3db080e4e..526a57900a 100644 --- a/Documentation/Components.md +++ b/Documentation/Components.md @@ -39,6 +39,17 @@ internally by Babylon Native components that need a cross-platform abstraction for GPU access and rendering work. However, no bgfx types are exposed by Babylon Native's APIs. +The Graphics device owns its window framebuffer separately from the underlying +bgfx device. Resizing or replacing a window updates that surface without +invalidating GPU resources. D3D11 caller-provided back-buffer views are imported +as texture attachments rather than replacing bgfx platform data. Screenshots and +continuous capture use the active back buffer; multiple requests in one frame +share a single readback. + +Only default framebuffer wrappers created without an explicit handle follow the +device's current window framebuffer. Explicit targets, including XR eye +framebuffers with default-back-buffer semantics, retain their supplied handles. + ### glslang [glslang](https://github.com/KhronosGroup/glslang) is the reference compiler diff --git a/Install/Install.cmake b/Install/Install.cmake index d0fb5b1ddd..16efea0eab 100644 --- a/Install/Install.cmake +++ b/Install/Install.cmake @@ -20,6 +20,7 @@ endfunction() function(install_bin) foreach(target IN LISTS ARGN) install(PROGRAMS "$" DESTINATION bin) + install(PROGRAMS $ DESTINATION bin) install(FILES "$/$$.pdb" DESTINATION bin OPTIONAL) endforeach() endfunction() @@ -43,7 +44,7 @@ endfunction() install_lib(arcana) ## bgfx -install_lib(bimg_encode bimg_decode bgfx bimg bx minz) +install_lib(bimg_encode bimg_decode bgfx bimg bx) ## glslang install_lib(GenericCodeGen glslang glslang-default-resource-limits MachineIndependent OGLCompiler OSDependent SPIRV) diff --git a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp index 5b8553f444..30784cc1d4 100644 --- a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp +++ b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,160 +8,6 @@ #include "ExternalTexture_Base.h" -// clang-format off - -// Copied from renderer_d3d.h -#define DXGI_FORMAT_ASTC_4X4_UNORM DXGI_FORMAT(134) -#define DXGI_FORMAT_ASTC_4X4_UNORM_SRGB DXGI_FORMAT(135) -#define DXGI_FORMAT_ASTC_5X4_UNORM DXGI_FORMAT(138) -#define DXGI_FORMAT_ASTC_5X4_UNORM_SRGB DXGI_FORMAT(139) -#define DXGI_FORMAT_ASTC_5X5_UNORM DXGI_FORMAT(142) -#define DXGI_FORMAT_ASTC_5X5_UNORM_SRGB DXGI_FORMAT(143) -#define DXGI_FORMAT_ASTC_6X5_UNORM DXGI_FORMAT(146) -#define DXGI_FORMAT_ASTC_6X5_UNORM_SRGB DXGI_FORMAT(147) -#define DXGI_FORMAT_ASTC_6X6_UNORM DXGI_FORMAT(150) -#define DXGI_FORMAT_ASTC_6X6_UNORM_SRGB DXGI_FORMAT(151) -#define DXGI_FORMAT_ASTC_8X5_UNORM DXGI_FORMAT(154) -#define DXGI_FORMAT_ASTC_8X5_UNORM_SRGB DXGI_FORMAT(155) -#define DXGI_FORMAT_ASTC_8X6_UNORM DXGI_FORMAT(158) -#define DXGI_FORMAT_ASTC_8X6_UNORM_SRGB DXGI_FORMAT(159) -#define DXGI_FORMAT_ASTC_8X8_UNORM DXGI_FORMAT(162) -#define DXGI_FORMAT_ASTC_8X8_UNORM_SRGB DXGI_FORMAT(163) -#define DXGI_FORMAT_ASTC_10X5_UNORM DXGI_FORMAT(166) -#define DXGI_FORMAT_ASTC_10X5_UNORM_SRGB DXGI_FORMAT(167) -#define DXGI_FORMAT_ASTC_10X6_UNORM DXGI_FORMAT(170) -#define DXGI_FORMAT_ASTC_10X6_UNORM_SRGB DXGI_FORMAT(171) -#define DXGI_FORMAT_ASTC_10X8_UNORM DXGI_FORMAT(174) -#define DXGI_FORMAT_ASTC_10X8_UNORM_SRGB DXGI_FORMAT(175) -#define DXGI_FORMAT_ASTC_10X10_UNORM DXGI_FORMAT(178) -#define DXGI_FORMAT_ASTC_10X10_UNORM_SRGB DXGI_FORMAT(179) -#define DXGI_FORMAT_ASTC_12X10_UNORM DXGI_FORMAT(182) -#define DXGI_FORMAT_ASTC_12X10_UNORM_SRGB DXGI_FORMAT(183) -#define DXGI_FORMAT_ASTC_12X12_UNORM DXGI_FORMAT(186) -#define DXGI_FORMAT_ASTC_12X12_UNORM_SRGB DXGI_FORMAT(187) - -// Copied from renderer_d3d11.cpp -namespace -{ - struct TextureFormatInfo - { - DXGI_FORMAT m_fmt; - DXGI_FORMAT m_fmtSrgb; - }; - - const TextureFormatInfo s_textureFormat[] = - { - { DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM_SRGB }, // BC1 - { DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM_SRGB }, // BC2 - { DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM_SRGB }, // BC3 - { DXGI_FORMAT_BC4_UNORM, DXGI_FORMAT_UNKNOWN }, // BC4 - { DXGI_FORMAT_BC4_SNORM, DXGI_FORMAT_UNKNOWN }, // BC4S - { DXGI_FORMAT_BC5_UNORM, DXGI_FORMAT_UNKNOWN }, // BC5 - { DXGI_FORMAT_BC5_SNORM, DXGI_FORMAT_UNKNOWN }, // BC5S - { DXGI_FORMAT_BC6H_SF16, DXGI_FORMAT_UNKNOWN }, // BC6H - { DXGI_FORMAT_BC6H_UF16, DXGI_FORMAT_UNKNOWN }, // BC6HU - { DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM_SRGB }, // BC7 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ETC1 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ETC2 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ETC2A - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ETC2A1 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // EACR11 UNORM - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // EACR11 SNORM - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // EACRG11 UNORM - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // EACRG11 SNORM - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC12 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC14 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC12A - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC14A - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC22 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // PTC24 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ATC - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ATCE - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // ATCI - { DXGI_FORMAT_ASTC_4X4_UNORM, DXGI_FORMAT_ASTC_4X4_UNORM_SRGB }, // ASTC4x4 - { DXGI_FORMAT_ASTC_5X4_UNORM, DXGI_FORMAT_ASTC_5X4_UNORM_SRGB }, // ASTC5x4 - { DXGI_FORMAT_ASTC_5X5_UNORM, DXGI_FORMAT_ASTC_5X5_UNORM_SRGB }, // ASTC5x5 - { DXGI_FORMAT_ASTC_6X5_UNORM, DXGI_FORMAT_ASTC_6X5_UNORM_SRGB }, // ASTC6x5 - { DXGI_FORMAT_ASTC_6X6_UNORM, DXGI_FORMAT_ASTC_6X6_UNORM_SRGB }, // ASTC6x6 - { DXGI_FORMAT_ASTC_8X5_UNORM, DXGI_FORMAT_ASTC_8X5_UNORM_SRGB }, // ASTC8x5 - { DXGI_FORMAT_ASTC_8X6_UNORM, DXGI_FORMAT_ASTC_8X6_UNORM_SRGB }, // ASTC8x6 - { DXGI_FORMAT_ASTC_8X8_UNORM, DXGI_FORMAT_ASTC_8X8_UNORM_SRGB }, // ASTC8x8 - { DXGI_FORMAT_ASTC_10X5_UNORM, DXGI_FORMAT_ASTC_10X5_UNORM_SRGB }, // ASTC10x5 - { DXGI_FORMAT_ASTC_10X6_UNORM, DXGI_FORMAT_ASTC_10X6_UNORM_SRGB }, // ASTC10x6 - { DXGI_FORMAT_ASTC_10X8_UNORM, DXGI_FORMAT_ASTC_10X8_UNORM_SRGB }, // ASTC10x8 - { DXGI_FORMAT_ASTC_10X10_UNORM, DXGI_FORMAT_ASTC_10X10_UNORM_SRGB}, // ASTC10x10 - { DXGI_FORMAT_ASTC_12X10_UNORM, DXGI_FORMAT_ASTC_12X10_UNORM_SRGB}, // ASTC12x10 - { DXGI_FORMAT_ASTC_12X12_UNORM, DXGI_FORMAT_ASTC_12X12_UNORM_SRGB}, // ASTC12x12 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // Unknown - { DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_UNKNOWN }, // R1 - { DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_UNKNOWN }, // A8 - { DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_UNKNOWN }, // R8 - { DXGI_FORMAT_R8_SINT, DXGI_FORMAT_UNKNOWN }, // R8I - { DXGI_FORMAT_R8_UINT, DXGI_FORMAT_UNKNOWN }, // R8U - { DXGI_FORMAT_R8_SNORM, DXGI_FORMAT_UNKNOWN }, // R8S - { DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_UNKNOWN }, // R16 - { DXGI_FORMAT_R16_SINT, DXGI_FORMAT_UNKNOWN }, // R16I - { DXGI_FORMAT_R16_UINT, DXGI_FORMAT_UNKNOWN }, // R16U - { DXGI_FORMAT_R16_FLOAT, DXGI_FORMAT_UNKNOWN }, // R16F - { DXGI_FORMAT_R16_SNORM, DXGI_FORMAT_UNKNOWN }, // R16S - { DXGI_FORMAT_R32_SINT, DXGI_FORMAT_UNKNOWN }, // R32I - { DXGI_FORMAT_R32_UINT, DXGI_FORMAT_UNKNOWN }, // R32U - { DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_UNKNOWN }, // R32F - { DXGI_FORMAT_R8G8_UNORM, DXGI_FORMAT_UNKNOWN }, // RG8 - { DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_UNKNOWN }, // RG8I - { DXGI_FORMAT_R8G8_UINT, DXGI_FORMAT_UNKNOWN }, // RG8U - { DXGI_FORMAT_R8G8_SNORM, DXGI_FORMAT_UNKNOWN }, // RG8S - { DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_UNKNOWN }, // RG16 - { DXGI_FORMAT_R16G16_SINT, DXGI_FORMAT_UNKNOWN }, // RG16I - { DXGI_FORMAT_R16G16_UINT, DXGI_FORMAT_UNKNOWN }, // RG16U - { DXGI_FORMAT_R16G16_FLOAT, DXGI_FORMAT_UNKNOWN }, // RG16F - { DXGI_FORMAT_R16G16_SNORM, DXGI_FORMAT_UNKNOWN }, // RG16S - { DXGI_FORMAT_R32G32_SINT, DXGI_FORMAT_UNKNOWN }, // RG32I - { DXGI_FORMAT_R32G32_UINT, DXGI_FORMAT_UNKNOWN }, // RG32U - { DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_UNKNOWN }, // RG32F - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // RGB8 - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // RGB8I - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // RGB8U - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // RGB8S - { DXGI_FORMAT_R9G9B9E5_SHAREDEXP, DXGI_FORMAT_UNKNOWN }, // RGB9E5F - { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB }, // BGRA8 - { DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB }, // RGBA8 - { DXGI_FORMAT_R8G8B8A8_SINT, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB }, // RGBA8I - { DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB }, // RGBA8U - { DXGI_FORMAT_R8G8B8A8_SNORM, DXGI_FORMAT_UNKNOWN }, // RGBA8S - { DXGI_FORMAT_R16G16B16A16_UNORM, DXGI_FORMAT_UNKNOWN }, // RGBA16 - { DXGI_FORMAT_R16G16B16A16_SINT, DXGI_FORMAT_UNKNOWN }, // RGBA16I - { DXGI_FORMAT_R16G16B16A16_UINT, DXGI_FORMAT_UNKNOWN }, // RGBA16U - { DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_UNKNOWN }, // RGBA16F - { DXGI_FORMAT_R16G16B16A16_SNORM, DXGI_FORMAT_UNKNOWN }, // RGBA16S - { DXGI_FORMAT_R32G32B32A32_SINT, DXGI_FORMAT_UNKNOWN }, // RGBA32I - { DXGI_FORMAT_R32G32B32A32_UINT, DXGI_FORMAT_UNKNOWN }, // RGBA32U - { DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_UNKNOWN }, // RGBA32F - { DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_UNKNOWN }, // B5G6R5 - { DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_UNKNOWN }, // R5G6B5 - { DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_UNKNOWN }, // BGRA4 - { DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_UNKNOWN }, // RGBA4 - { DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_UNKNOWN }, // BGR5A1 - { DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB5A1 - { DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB10A2 - { DXGI_FORMAT_R10G10B10A2_UINT, DXGI_FORMAT_UNKNOWN }, // RGB10A2U - { DXGI_FORMAT_R11G11B10_FLOAT, DXGI_FORMAT_UNKNOWN }, // RG11B10F - { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // UnknownDepth - { DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D16 - { DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D24 - { DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D24S8 - { DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D32 - { DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D16F - { DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D24F - { DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D32F - { DXGI_FORMAT_R32G8X24_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D32FS8 - { DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_UNKNOWN }, // D0S8 - }; - static_assert(bgfx::TextureFormat::Count == BX_COUNTOF(s_textureFormat)); -} - -// clang-format on - namespace Babylon::Plugins { uintptr_t NativeTextureHandle(Graphics::TextureT ptr) @@ -212,18 +59,12 @@ namespace Babylon::Plugins } DXGI_FORMAT targetFormat = overrideFormat.has_value() ? overrideFormat.value() : desc.Format; - for (int i = 0; i < BX_COUNTOF(s_textureFormat); ++i) + if (const auto format = Graphics::D3D11TextureFormats::TryGetBgfxTextureFormat(targetFormat)) { - const auto& format = s_textureFormat[i]; - if (format.m_fmt == targetFormat || format.m_fmtSrgb == targetFormat) + info.Format = format->Format; + if (format->Srgb) { - info.Format = static_cast(i); - if (format.m_fmtSrgb == targetFormat) - { - info.Flags |= BGFX_TEXTURE_SRGB; - } - - break; + info.Flags |= BGFX_TEXTURE_SRGB; } } } diff --git a/Plugins/NativeCamera/Source/Android/CameraDevice.cpp b/Plugins/NativeCamera/Source/Android/CameraDevice.cpp index a922b0d820..2584c7cfc2 100644 --- a/Plugins/NativeCamera/Source/Android/CameraDevice.cpp +++ b/Plugins/NativeCamera/Source/Android/CameraDevice.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -102,6 +103,9 @@ namespace Babylon::Plugins GLuint cameraRGBATextureId{}; GLuint cameraShaderProgramId{}; GLuint frameBufferId{}; + GLuint importedCameraRGBATextureId{}; + Graphics::Texture* bgfxTexture{}; + bgfx::TextureHandle bgfxTextureHandle{bgfx::kInvalidHandle}; EGLContext context{EGL_NO_CONTEXT}; EGLDisplay display{}; @@ -431,7 +435,7 @@ namespace Babylon::Plugins return cameraDevices; } - CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(bgfx::TextureHandle textureHandle) + CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(Graphics::Texture& texture) { EGLContext currentContext = eglGetCurrentContext(); if (m_impl->context != EGL_NO_CONTEXT) @@ -512,9 +516,24 @@ namespace Babylon::Plugins throw std::runtime_error{"Unable to make current shared GL context for camera texture."}; } - arcana::make_task(m_impl->deviceContext->BeforeRenderScheduler(), arcana::cancellation::none(), [rgbaTextureId = m_impl->cameraRGBATextureId, textureHandle] { - bgfx::overrideInternal(textureHandle, rgbaTextureId); - }); + if (m_impl->bgfxTexture != &texture || + m_impl->bgfxTextureHandle.idx != texture.Handle().idx || + m_impl->importedCameraRGBATextureId != m_impl->cameraRGBATextureId) + { + const auto textureWidth = static_cast(!sensorIsPortrait ? m_impl->cameraDimensions.width : m_impl->cameraDimensions.height); + const auto textureHeight = static_cast(!sensorIsPortrait ? m_impl->cameraDimensions.height : m_impl->cameraDimensions.width); + texture.Create2D( + textureWidth, + textureHeight, + texture.HasMips(), + texture.NumLayers(), + texture.Format(), + texture.Flags(), + m_impl->cameraRGBATextureId); + m_impl->bgfxTexture = &texture; + m_impl->bgfxTextureHandle = texture.Handle(); + m_impl->importedCameraRGBATextureId = m_impl->cameraRGBATextureId; + } return !sensorIsPortrait ? CameraDimensions{m_impl->cameraDimensions.width, m_impl->cameraDimensions.height} diff --git a/Plugins/NativeCamera/Source/Apple/CameraDevice.mm b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm index 3fc80f814e..b130e56ed5 100644 --- a/Plugins/NativeCamera/Source/Apple/CameraDevice.mm +++ b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -236,8 +238,9 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], id commandQueue{}; id currentCommandBuffer{}; bool isInitialized{false}; - bool refreshBgfxHandle{true}; - bgfx::TextureHandle textureHandle{}; + bool refreshBgfxTexture{true}; + Graphics::Texture* bgfxTexture{}; + bgfx::TextureHandle bgfxTextureHandle{bgfx::kInvalidHandle}; arcana::background_dispatcher<32> cameraSessionDispatcher{}; std::shared_ptr cancellationSource{std::make_shared()}; @@ -416,8 +419,8 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], m_impl->isInitialized = true; } else { - // Always refresh the bgfx handle to point to textureRGBA on re-open. - m_impl->refreshBgfxHandle = true; + // Always refresh the bgfx texture to point to textureRGBA on re-open. + m_impl->refreshBgfxTexture = true; } // Construct the camera texture delegate, which is responsible for handling updates for device orientation and the capture session. @@ -577,143 +580,150 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], }); } - CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(bgfx::TextureHandle textureHandle) + CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(Graphics::Texture& texture) { - // Hook into AfterRender to copy over the texture, ensuring that the textureHandle has already been initialized by bgfx. - // Capture the cancellation token so that the shared pointer is kept alive when arcana checks internally for cancellation. - arcana::make_task(m_impl->deviceContext->AfterRenderScheduler(), *m_impl->cancellationSource, [this, textureHandle, cancellationSource{m_impl->cancellationSource}] { - id textureY{}; - id textureCbCr{}; - int64_t width{0}; - int64_t height{0}; - - @synchronized(m_impl->cameraTextureDelegate) { - textureY = [m_impl->cameraTextureDelegate getCameraTextureY]; - textureCbCr = [m_impl->cameraTextureDelegate getCameraTextureCbCr]; + id textureY{}; + id textureCbCr{}; + int64_t width{0}; + int64_t height{0}; + @synchronized(m_impl->cameraTextureDelegate) { + textureY = [m_impl->cameraTextureDelegate getCameraTextureY]; + textureCbCr = [m_impl->cameraTextureDelegate getCameraTextureCbCr]; + + switch (m_impl->cameraTextureDelegate->Orientation) + { + case VideoOrientation::LandscapeRight: + case VideoOrientation::LandscapeLeft: + width = [textureY width]; + height = [textureY height]; + break; + case VideoOrientation::Portrait: + case VideoOrientation::PortraitUpsideDown: + // In portrait orientation the camera sensor is rotated 90 degrees so the width and height should be swapped + width = [textureY height]; + height = [textureY width]; + break; + } + } + + // Skip processing this frame if width and height are invalid. + if (width == 0 || height == 0) { + return CameraDimensions{m_impl->cameraDimensions.width, m_impl->cameraDimensions.height}; + } + + // Check if we've been handed a different texture or if its bgfx handle was recreated. + if (m_impl->bgfxTexture != &texture || m_impl->bgfxTextureHandle.idx != texture.Handle().idx) + { + m_impl->refreshBgfxTexture = true; + } + + // Recreate the output texture when the camera dimensions change. + if (m_impl->textureRGBA == nil || m_impl->cameraDimensions.width != width || m_impl->cameraDimensions.height != height) + { + MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:width height:height mipmapped:NO]; + textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; + m_impl->textureRGBA = [m_impl->metalDevice newTextureWithDescriptor:textureDescriptor]; + m_impl->cameraDimensions.width = static_cast(width); + m_impl->cameraDimensions.height = static_cast(height); + m_impl->refreshBgfxTexture = true; + } + + if (textureY != nil && textureCbCr != nil && m_impl->textureRGBA != nil) + { + m_impl->currentCommandBuffer = [m_impl->commandQueue commandBuffer]; + m_impl->currentCommandBuffer.label = @"NativeCameraCommandBuffer"; + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + + if (renderPassDescriptor != nil) { + // Attach the color texture, on which we'll draw the camera texture (so no need to clear on load). + renderPassDescriptor.colorAttachments[0].texture = m_impl->textureRGBA; + renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionDontCare; + renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + + // Create and end the render encoder. + id renderEncoder = [m_impl->currentCommandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + renderEncoder.label = @"NativeCameraEncoder"; + + // Set the shader pipeline. + [renderEncoder setRenderPipelineState:m_impl->cameraPipelineState]; + + // Set the vertex & UV data based on current orientation switch (m_impl->cameraTextureDelegate->Orientation) { - case VideoOrientation::LandscapeRight: case VideoOrientation::LandscapeLeft: - width = [textureY width]; - height = [textureY height]; + if (m_impl->avDevice.position == AVCaptureDevicePositionFront) + { + // The front camera sensor is oriented 180 out of sync from the rear sensor on iOS devices. Swap landscape orientations. + [renderEncoder setVertexBytes:vertices_landscape_right length:sizeof(vertices_landscape_right) atIndex:0]; + } + else + { + [renderEncoder setVertexBytes:vertices_landscape_left length:sizeof(vertices_landscape_left) atIndex:0]; + } break; case VideoOrientation::Portrait: + [renderEncoder setVertexBytes:vertices_portrait length:sizeof(vertices_portrait) atIndex:0]; + break; case VideoOrientation::PortraitUpsideDown: - // In portrait orientation the camera sensor is rotated 90 degrees so the width and height should be swapped - width = [textureY height]; - height = [textureY width]; + [renderEncoder setVertexBytes:vertices_portrait_upsideddown length:sizeof(vertices_portrait_upsideddown) atIndex:0]; + break; + case VideoOrientation::LandscapeRight: + if (m_impl->avDevice.position == AVCaptureDevicePositionFront) + { + // The front camera sensor is oriented 180 out of sync from the rear sensor on iOS devices. Swap landscape orientations. + [renderEncoder setVertexBytes:vertices_landscape_left length:sizeof(vertices_landscape_left) atIndex:0]; + } + else + { + [renderEncoder setVertexBytes:vertices_landscape_right length:sizeof(vertices_landscape_right) atIndex:0]; + } break; } - } - - // Skip processing this frame if width and height are invalid. - if (width == 0 || height == 0) { - return; - } - - // Check if the we've been handed a new texture handle and if so refresh our override - if (m_impl->textureHandle.idx != textureHandle.idx) - { - m_impl->refreshBgfxHandle = true; - m_impl->textureHandle = textureHandle; - } - - // Recreate the output texture when the camera dimensions change. - if (m_impl->textureRGBA == nil || m_impl->cameraDimensions.width != width || m_impl->cameraDimensions.height != height) - { - MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:width height:height mipmapped:NO]; - textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; - m_impl->textureRGBA = [m_impl->metalDevice newTextureWithDescriptor:textureDescriptor]; - m_impl->cameraDimensions.width = static_cast(width); - m_impl->cameraDimensions.height = static_cast(height); - // Setting up the bgfx texture may fail if the textureHandle hasn't been initialized in a bgfx::frame call yet, if so try agin on - // the next frame to override it. - m_impl->refreshBgfxHandle = bgfx::overrideInternal(textureHandle, reinterpret_cast(m_impl->textureRGBA)) == 0; - } - else if (m_impl->refreshBgfxHandle) - { - m_impl->refreshBgfxHandle = bgfx::overrideInternal(textureHandle, reinterpret_cast(m_impl->textureRGBA)) == 0; - } - - if (textureY != nil && textureCbCr != nil && m_impl->textureRGBA != nil && !m_impl->refreshBgfxHandle) - { - m_impl->currentCommandBuffer = [m_impl->commandQueue commandBuffer]; - m_impl->currentCommandBuffer.label = @"NativeCameraCommandBuffer"; - MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; - if (renderPassDescriptor != nil) { - // Attach the color texture, on which we'll draw the camera texture (so no need to clear on load). - renderPassDescriptor.colorAttachments[0].texture = m_impl->textureRGBA; - renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionDontCare; - renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + // Set the textures. + [renderEncoder setFragmentTexture:textureY atIndex:1]; + [renderEncoder setFragmentTexture:textureCbCr atIndex:2]; - // Create and end the render encoder. - id renderEncoder = [m_impl->currentCommandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - renderEncoder.label = @"NativeCameraEncoder"; + // Draw the triangles. + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; - // Set the shader pipeline. - [renderEncoder setRenderPipelineState:m_impl->cameraPipelineState]; + [renderEncoder endEncoding]; - // Set the vertex & UV data based on current orientation - switch (m_impl->cameraTextureDelegate->Orientation) - { - case VideoOrientation::LandscapeLeft: - if (m_impl->avDevice.position == AVCaptureDevicePositionFront) - { - // The front camera sensor is oriented 180 out of sync from the rear sensor on iOS devices. Swap landscape orientations. - [renderEncoder setVertexBytes:vertices_landscape_right length:sizeof(vertices_landscape_right) atIndex:0]; - } - else - { - [renderEncoder setVertexBytes:vertices_landscape_left length:sizeof(vertices_landscape_left) atIndex:0]; - } - break; - case VideoOrientation::Portrait: - [renderEncoder setVertexBytes:vertices_portrait length:sizeof(vertices_portrait) atIndex:0]; - break; - case VideoOrientation::PortraitUpsideDown: - [renderEncoder setVertexBytes:vertices_portrait_upsideddown length:sizeof(vertices_portrait_upsideddown) atIndex:0]; - break; - case VideoOrientation::LandscapeRight: - if (m_impl->avDevice.position == AVCaptureDevicePositionFront) - { - // The front camera sensor is oriented 180 out of sync from the rear sensor on iOS devices. Swap landscape orientations. - [renderEncoder setVertexBytes:vertices_landscape_left length:sizeof(vertices_landscape_left) atIndex:0]; - } - else - { - [renderEncoder setVertexBytes:vertices_landscape_right length:sizeof(vertices_landscape_right) atIndex:0]; - } - break; + [m_impl->currentCommandBuffer addCompletedHandler:^(id) { + if (textureY != nil) { + [textureY setPurgeableState:MTLPurgeableStateEmpty]; } - // Set the textures. - [renderEncoder setFragmentTexture:textureY atIndex:1]; - [renderEncoder setFragmentTexture:textureCbCr atIndex:2]; - - // Draw the triangles. - [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; + if (textureCbCr != nil) { + [textureCbCr setPurgeableState:MTLPurgeableStateEmpty]; + } + }]; + } - [renderEncoder endEncoding]; + // Finalize rendering here & push the command buffer to the GPU. + [m_impl->currentCommandBuffer commit]; - [m_impl->currentCommandBuffer addCompletedHandler:^(id) { - if (textureY != nil) { - [textureY setPurgeableState:MTLPurgeableStateEmpty]; - } + [m_impl->currentCommandBuffer waitUntilCompleted]; + } - if (textureCbCr != nil) { - [textureCbCr setPurgeableState:MTLPurgeableStateEmpty]; - } - }]; - } + // Import on the calling thread while the Texture wrapper is still owned by the caller. + if (m_impl->refreshBgfxTexture && m_impl->textureRGBA != nil) + { + texture.Create2D( + static_cast(width), + static_cast(height), + texture.HasMips(), + texture.NumLayers(), + texture.Format(), + texture.Flags(), + reinterpret_cast(m_impl->textureRGBA), + std::shared_ptr{(__bridge_retained void*)m_impl->textureRGBA, [](void* resource) { CFRelease(resource); }}); + m_impl->bgfxTexture = &texture; + m_impl->bgfxTextureHandle = texture.Handle(); + m_impl->refreshBgfxTexture = false; + } - // Finalize rendering here & push the command buffer to the GPU. - [m_impl->currentCommandBuffer commit]; - - [m_impl->currentCommandBuffer waitUntilCompleted]; - } - }); // To match the web implementation if the sensor is rotated into a portrait orientation then the width and height // of the video should be swapped // NOTE: This code returns (width, height) independently of the VideoOrientation. As no bug as been reported, this code @@ -825,10 +835,10 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], // to a deadlock where Babylon is waiting for the frame to finish render on the main thread and AVCaptureSession::stopRunning is waiting // for the main thread to free up while blocking the current frame from rendering. // - // Capturing textureRGBA, textureDelegate, and textureCache is done here because it's used in bgfx::overrideInternal but due to ARC being enabled in this project the lifetime of the texture - // needs to be maintained until after the render pass. Otherwise bgfx will try to access a destroyed texture handle during the render pass. + // Keep capture resources alive until the session stops. Imported output textures + // are retained separately by their Graphics::Texture wrappers. arcana::make_task(m_impl->deviceContext->AfterRenderScheduler(), arcana::cancellation::none(), - [avCaptureSession = m_impl->avCaptureSession, textureRGBA = m_impl->textureRGBA, textureDelegate = m_impl->cameraTextureDelegate, textureCache = m_impl->textureCache] + [avCaptureSession = m_impl->avCaptureSession, textureDelegate = m_impl->cameraTextureDelegate, textureCache = m_impl->textureCache] { [avCaptureSession stopRunning]; diff --git a/Plugins/NativeCamera/Source/CameraDevice.h b/Plugins/NativeCamera/Source/CameraDevice.h index d644623b67..5003f2168e 100644 --- a/Plugins/NativeCamera/Source/CameraDevice.h +++ b/Plugins/NativeCamera/Source/CameraDevice.h @@ -7,6 +7,11 @@ #include #include "Capability.h" +namespace Babylon::Graphics +{ + class Texture; +} + namespace Babylon::Plugins { enum class RedEyeReduction @@ -94,7 +99,8 @@ namespace Babylon::Plugins arcana::task OpenAsync(const CameraTrack& track); void Close(); - CameraDimensions UpdateCameraTexture(bgfx::TextureHandle textureHandle); + // Access to the supplied Texture wrapper must complete before this call returns. + CameraDimensions UpdateCameraTexture(Graphics::Texture& texture); TakePhotoTask TakePhotoAsync(PhotoSettings photoSettings); const std::vector& SupportedResolutions() const; diff --git a/Plugins/NativeCamera/Source/MediaStream.cpp b/Plugins/NativeCamera/Source/MediaStream.cpp index 209a1f3024..997516b624 100644 --- a/Plugins/NativeCamera/Source/MediaStream.cpp +++ b/Plugins/NativeCamera/Source/MediaStream.cpp @@ -352,7 +352,7 @@ namespace Babylon::Plugins m_cameraDevice = nullptr; } - bool MediaStream::UpdateTexture(bgfx::TextureHandle textureHandle) + bool MediaStream::UpdateTexture(Graphics::Texture& texture) { bool dimensionsChanged = false; @@ -362,7 +362,7 @@ namespace Babylon::Plugins return dimensionsChanged; } - auto cameraDimensions{m_cameraDevice->UpdateCameraTexture(textureHandle)}; + auto cameraDimensions{m_cameraDevice->UpdateCameraTexture(texture)}; if (this->Width != cameraDimensions.width || this->Height != cameraDimensions.height) { diff --git a/Plugins/NativeCamera/Source/MediaStream.h b/Plugins/NativeCamera/Source/MediaStream.h index 74593a74c9..213bc7eb96 100644 --- a/Plugins/NativeCamera/Source/MediaStream.h +++ b/Plugins/NativeCamera/Source/MediaStream.h @@ -33,7 +33,7 @@ namespace Babylon::Plugins void Stop(const Napi::CallbackInfo& info); // Update the camera texture and return true if the dimensions have changed, false otherwise - bool UpdateTexture(bgfx::TextureHandle textureHandle); + bool UpdateTexture(Graphics::Texture& texture); std::shared_ptr CameraDevice() const { diff --git a/Plugins/NativeCamera/Source/NativeCamera.cpp b/Plugins/NativeCamera/Source/NativeCamera.cpp index 906da78a80..03146e3881 100644 --- a/Plugins/NativeCamera/Source/NativeCamera.cpp +++ b/Plugins/NativeCamera/Source/NativeCamera.cpp @@ -38,10 +38,10 @@ namespace Babylon::Plugins::Internal void UpdateVideoTexture(const Napi::CallbackInfo& info) { - const auto& texture = *info[0].As>().Get(); + auto& texture = *info[0].As>().Get(); auto videoObject = NativeVideo::Unwrap(info[1].As()); - videoObject->UpdateTexture(texture.Handle()); + videoObject->UpdateTexture(texture); } }; } diff --git a/Plugins/NativeCamera/Source/NativeVideo.cpp b/Plugins/NativeCamera/Source/NativeVideo.cpp index 5198f357d9..0a1ca34949 100644 --- a/Plugins/NativeCamera/Source/NativeVideo.cpp +++ b/Plugins/NativeCamera/Source/NativeVideo.cpp @@ -77,12 +77,12 @@ namespace Babylon::Plugins return Napi::Value::From(Env(), 1u); } - void NativeVideo::UpdateTexture(bgfx::TextureHandle textureHandle) + void NativeVideo::UpdateTexture(Graphics::Texture& texture) { // Only update the texture if we're playing and the srcObject is an instance of a MediaStream if (m_IsPlaying && !m_streamObject.Value().IsNull() && !m_streamObject.Value().IsUndefined()) { - if (MediaStream::Unwrap(m_streamObject.Value())->UpdateTexture(textureHandle)) + if (MediaStream::Unwrap(m_streamObject.Value())->UpdateTexture(texture)) { // The video dimensions have changed, raise the resize event to observers RaiseEvent("resize"); diff --git a/Plugins/NativeCamera/Source/NativeVideo.h b/Plugins/NativeCamera/Source/NativeVideo.h index ab8e27ca88..7354d07535 100644 --- a/Plugins/NativeCamera/Source/NativeVideo.h +++ b/Plugins/NativeCamera/Source/NativeVideo.h @@ -20,7 +20,7 @@ namespace Babylon::Plugins NativeVideo(const Napi::CallbackInfo& info); ~NativeVideo() = default; - void UpdateTexture(bgfx::TextureHandle textureHandle); + void UpdateTexture(Graphics::Texture& texture); private: void AddEventListener(const Napi::CallbackInfo& info); diff --git a/Plugins/NativeCamera/Source/Unix/CameraDevice.cpp b/Plugins/NativeCamera/Source/Unix/CameraDevice.cpp index 241f880cbe..9278b8b3ea 100644 --- a/Plugins/NativeCamera/Source/Unix/CameraDevice.cpp +++ b/Plugins/NativeCamera/Source/Unix/CameraDevice.cpp @@ -29,7 +29,7 @@ namespace Babylon::Plugins throw std::runtime_error{"HW Camera not implemented for this platform."}; } - CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(bgfx::TextureHandle /*textureHandle*/) + CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(Graphics::Texture& /*texture*/) { throw std::runtime_error{"HW Camera not implemented for this platform."}; } diff --git a/Plugins/NativeCamera/Source/Win32/CameraDevice.cpp b/Plugins/NativeCamera/Source/Win32/CameraDevice.cpp index 3930a5223f..f4532da315 100644 --- a/Plugins/NativeCamera/Source/Win32/CameraDevice.cpp +++ b/Plugins/NativeCamera/Source/Win32/CameraDevice.cpp @@ -31,7 +31,7 @@ namespace Babylon::Plugins throw std::runtime_error{"HW Camera not implemented for this platform."}; } - CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(bgfx::TextureHandle /*textureHandle*/) + CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(Graphics::Texture& /*texture*/) { throw std::runtime_error{"HW Camera not implemented for this platform."}; } diff --git a/Plugins/NativeCamera/Source/WinRT/CameraDevice.cpp b/Plugins/NativeCamera/Source/WinRT/CameraDevice.cpp index 3930a5223f..f4532da315 100644 --- a/Plugins/NativeCamera/Source/WinRT/CameraDevice.cpp +++ b/Plugins/NativeCamera/Source/WinRT/CameraDevice.cpp @@ -31,7 +31,7 @@ namespace Babylon::Plugins throw std::runtime_error{"HW Camera not implemented for this platform."}; } - CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(bgfx::TextureHandle /*textureHandle*/) + CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(Graphics::Texture& /*texture*/) { throw std::runtime_error{"HW Camera not implemented for this platform."}; } diff --git a/Plugins/NativeEngine/CMakeLists.txt b/Plugins/NativeEngine/CMakeLists.txt index 251fc2d4a8..400bbb8073 100644 --- a/Plugins/NativeEngine/CMakeLists.txt +++ b/Plugins/NativeEngine/CMakeLists.txt @@ -31,7 +31,6 @@ target_link_libraries(NativeEngine PRIVATE arcana PRIVATE bgfx PRIVATE bx - PRIVATE minz PRIVATE GraphicsDevice PRIVATE GraphicsDeviceContext PRIVATE JsRuntime) diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index 1fbef693cb..00043891b1 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -2510,11 +2510,11 @@ namespace Babylon for (Graphics::Texture* texture : colorTextures) { - // bgfx validation now asserts when trying to use BGFX_RESOLVE_AUTO_GEN_MIPS with a texture that doesn't have the BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN flag, + // bgfx validation now asserts when trying to use BGFX_ATTACHMENT_AUTO_GEN_MIPS with a texture that doesn't have the BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN flag, // but before it would just ignore the flag and not generate mips without any warning. This prevents validation assert, but rendering might be broken if autogen // mips were expected. Basically this change preserves previous behavior. attachments[numAttachments++].init(texture->Handle(), bgfx::Access::Write, 0, 1, 0 - , 0 != (caps->formats[texture->Format()] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN) ? BGFX_RESOLVE_AUTO_GEN_MIPS : BGFX_RESOLVE_NONE + , 0 != (caps->formats[texture->Format()] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN) ? BGFX_ATTACHMENT_AUTO_GEN_MIPS : BGFX_ATTACHMENT_NONE ); } @@ -2560,7 +2560,7 @@ namespace Babylon // only allows mipmaps resolve step when mipmapping is asked and for the color texture, not the depth. // https://github.com/bkaradzic/bgfx/blob/2c21f68998595fa388e25cb6527e82254d0e9bff/src/renderer_d3d11.cpp#L4525 depthStencilAttachmentIndex = numAttachments; - attachments[numAttachments++].init(depthStencilTextureHandle, bgfx::Access::Write, 0, 1, 0, BGFX_RESOLVE_NONE); + attachments[numAttachments++].init(depthStencilTextureHandle, bgfx::Access::Write, 0, 1, 0, BGFX_ATTACHMENT_NONE); } bgfx::FrameBufferHandle frameBufferHandle = bgfx::createFrameBuffer(numAttachments, attachments.data()); diff --git a/Plugins/NativeEngine/Source/VertexArray.cpp b/Plugins/NativeEngine/Source/VertexArray.cpp index 4811503e1c..aef3e1fea4 100644 --- a/Plugins/NativeEngine/Source/VertexArray.cpp +++ b/Plugins/NativeEngine/Source/VertexArray.cpp @@ -42,13 +42,7 @@ namespace Babylon throw std::runtime_error{"Unsupported vertex buffer attribute type or normalized flag"}; } - // Check if instancing is supported. const bgfx::Caps* caps = bgfx::getCaps(); - const bool instancingSupported = 0 != (BGFX_CAPS_INSTANCING & caps->supported); - if (!instancingSupported) - { - throw std::runtime_error{"Instancing is not supported"}; - } // Use the runtime cap, not MAX_INSTANCE_DATA_SLOT_COUNT: backends clamp maxInstanceData // to the device's maxVertexAttributes during init, so the compile-time value is a @@ -95,9 +89,7 @@ namespace Babylon void VertexArray::SetVertexBuffers(bgfx::Encoder* encoder, uint32_t startVertex, uint32_t numVertices, uint32_t instanceCount, const VertexBuffer::InstanceDataLayout& instanceDataLayout) { - // Check if instancing is supported. - const bool instancingSupported = 0 != (BGFX_CAPS_INSTANCING & bgfx::getCaps()->supported); - if (!m_vertexBufferInstances.empty() && instancingSupported) + if (!m_vertexBufferInstances.empty()) { bgfx::InstanceDataBuffer instanceDataBuffer{}; VertexBuffer::BuildInstanceDataBuffer(instanceDataBuffer, m_vertexBufferInstances, instanceCount, instanceDataLayout); diff --git a/Plugins/NativeXr/Source/NativeXrImpl.cpp b/Plugins/NativeXr/Source/NativeXrImpl.cpp index 3ed986700b..f0f93ed343 100644 --- a/Plugins/NativeXr/Source/NativeXrImpl.cpp +++ b/Plugins/NativeXr/Source/NativeXrImpl.cpp @@ -249,6 +249,8 @@ namespace Babylon // If a texture width or height is 0, bgfx will assert (can't create 0 sized texture). Asserting here instead of deeper in bgfx rendering. // Depth (numLayers) can be 0, bgfx will just reinterpret it as max(numLayers, 1). + assert(view.ColorTexturePointer != nullptr); + assert(view.DepthTexturePointer != nullptr); assert(view.ColorTextureSize.Width != 0); assert(view.ColorTextureSize.Height != 0); assert(view.ColorTextureSize.Width == view.DepthTextureSize.Width); @@ -259,23 +261,38 @@ namespace Babylon const auto textureHeight = static_cast(view.ColorTextureSize.Height); const auto textureLayers = std::max(static_cast(1), static_cast(view.ColorTextureSize.Depth)); - // Create textures with the desired size. It will be freed and replaced with overrideInternal call - // This is mandatory as overrideInternal do not update texture size. - // And size is used for determining viewport when rendering to texture. + // Create bgfx handles that directly import the runtime-owned XR textures. The declared + // dimensions and layers are used to determine the viewport and per-eye array slices. auto colorTextureFormat = XrTextureFormatToBgfxFormat(view.ColorTextureFormat); - auto colorTexture = bgfx::createTexture2D(textureWidth, textureHeight, false, textureLayers, colorTextureFormat, BGFX_TEXTURE_RT); + auto colorTexture = bgfx::createTexture2D( + textureWidth, + textureHeight, + false, + textureLayers, + colorTextureFormat, + BGFX_TEXTURE_RT, + nullptr, + reinterpret_cast(viewConfig.ColorTexturePointer)); m_sessionState->GraphicsContext.AddTexture(colorTexture, textureWidth, textureHeight, false, textureLayers, colorTextureFormat); auto depthTextureFormat = XrTextureFormatToBgfxFormat(view.DepthTextureFormat); - auto depthTexture = bgfx::createTexture2D(textureWidth, textureHeight, false, textureLayers, depthTextureFormat, BGFX_TEXTURE_RT); + auto depthTexture = bgfx::createTexture2D( + textureWidth, + textureHeight, + false, + textureLayers, + depthTextureFormat, + BGFX_TEXTURE_RT, + nullptr, + reinterpret_cast(viewConfig.DepthTexturePointer)); m_sessionState->GraphicsContext.AddTexture(depthTexture, textureWidth, textureHeight, false, textureLayers, depthTextureFormat); auto requiresAppClear = view.RequiresAppClear; - arcana::make_task(m_sessionState->GraphicsContext.AfterRenderScheduler(), arcana::cancellation::none(), [colorTexture, depthTexture, &viewConfig]() { - bgfx::overrideInternal(colorTexture, reinterpret_cast(viewConfig.ColorTexturePointer)); - bgfx::overrideInternal(depthTexture, reinterpret_cast(viewConfig.DepthTexturePointer)); - }).then(m_runtimeScheduler, m_sessionState->CancellationSource, [this, thisRef{shared_from_this()}, colorTexture, depthTexture, colorTextureFormat, requiresAppClear, &viewConfig]() { + // Wait for the current bgfx frame to submit the external texture creation commands + // before publishing framebuffers that reference those handles. + arcana::make_task(m_sessionState->GraphicsContext.AfterRenderScheduler(), arcana::cancellation::none(), [] {}) + .then(m_runtimeScheduler, m_sessionState->CancellationSource, [this, thisRef{shared_from_this()}, colorTexture, depthTexture, colorTextureFormat, requiresAppClear, &viewConfig]() { const auto eyeCount = std::max(static_cast(1), static_cast(viewConfig.ViewTextureSize.Depth)); // TODO (rgerd): Remove old framebuffers from resource table? viewConfig.FrameBuffers.resize(eyeCount); @@ -302,16 +319,16 @@ namespace Babylon for (uint16_t eyeIdx = 0; eyeIdx < eyeCount; eyeIdx++) { - // See NativeEngine::CreateFrameBuffer: gate BGFX_RESOLVE_AUTO_GEN_MIPS on format caps and - // always pass BGFX_RESOLVE_NONE for depth (depth formats don't support autogen mips). + // See NativeEngine::CreateFrameBuffer: gate BGFX_ATTACHMENT_AUTO_GEN_MIPS on format caps and + // always pass BGFX_ATTACHMENT_NONE for depth (depth formats don't support autogen mips). const bgfx::Caps* caps = bgfx::getCaps(); - const uint8_t colorResolve = 0 != (caps->formats[colorTextureFormat] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN) - ? BGFX_RESOLVE_AUTO_GEN_MIPS - : BGFX_RESOLVE_NONE; + const uint8_t colorAttachmentFlags = 0 != (caps->formats[colorTextureFormat] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN) + ? BGFX_ATTACHMENT_AUTO_GEN_MIPS + : BGFX_ATTACHMENT_NONE; std::array attachments{}; - attachments[0].init(colorTexture, bgfx::Access::Write, eyeIdx, 1, 0, colorResolve); - attachments[1].init(depthTexture, bgfx::Access::Write, eyeIdx, 1, 0, BGFX_RESOLVE_NONE); + attachments[0].init(colorTexture, bgfx::Access::Write, eyeIdx, 1, 0, colorAttachmentFlags); + attachments[1].init(depthTexture, bgfx::Access::Write, eyeIdx, 1, 0, BGFX_ATTACHMENT_NONE); auto frameBufferHandle = bgfx::createFrameBuffer(static_cast(attachments.size()), attachments.data(), false); diff --git a/Plugins/ShaderCompiler/CMakeLists.txt b/Plugins/ShaderCompiler/CMakeLists.txt index 8afb6e318d..b254d13e79 100644 --- a/Plugins/ShaderCompiler/CMakeLists.txt +++ b/Plugins/ShaderCompiler/CMakeLists.txt @@ -43,6 +43,52 @@ endif() if(GRAPHICS_API STREQUAL "D3D11") target_link_libraries(ShaderCompiler PRIVATE "d3dcompiler.lib") +elseif(WIN32 AND GRAPHICS_API STREQUAL "D3D12") + if(CMAKE_VS_PLATFORM_NAME) + set(DXC_TARGET_ARCHITECTURE "${CMAKE_VS_PLATFORM_NAME}") + elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID) + set(DXC_TARGET_ARCHITECTURE "${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}") + else() + set(DXC_TARGET_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") + endif() + string(TOLOWER "${DXC_TARGET_ARCHITECTURE}" DXC_TARGET_ARCHITECTURE_LOWER) + + unset(DXC_RUNTIME_SKIP_REASON) + if(WINDOWS_STORE) + set(DXC_RUNTIME_SKIP_REASON "UWP targets require platform-specific packaging") + elseif(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) + set(DXC_RUNTIME_SKIP_REASON "the target uses ${CMAKE_SIZEOF_VOID_P}-byte pointers") + elseif(NOT DXC_TARGET_ARCHITECTURE_LOWER MATCHES "^(x64|amd64|x86_64)$") + set(DXC_RUNTIME_SKIP_REASON "the target architecture is '${DXC_TARGET_ARCHITECTURE}'") + endif() + + if(DXC_RUNTIME_SKIP_REASON) + message(WARNING + "The bundled bgfx DirectX Shader Compiler runtime is x64 desktop-only; " + "automatic deployment is disabled because ${DXC_RUNTIME_SKIP_REASON}. " + "Provide a compatible dxcompiler.dll and dxil.dll through the platform's existing deployment mechanism.") + else() + set(DXC_RUNTIME_DIR "${BGFX_DIR}/tools/bin/windows") + set(DXC_COMPILER_RUNTIME "${DXC_RUNTIME_DIR}/dxcompiler.dll") + set(DXC_VALIDATOR_RUNTIME "${DXC_RUNTIME_DIR}/dxil.dll") + foreach(DXC_RUNTIME_FILE "${DXC_COMPILER_RUNTIME}" "${DXC_VALIDATOR_RUNTIME}") + if(NOT EXISTS "${DXC_RUNTIME_FILE}") + message(FATAL_ERROR "D3D12 shader compilation requires ${DXC_RUNTIME_FILE}") + endif() + endforeach() + + add_library(ShaderCompiler::dxil SHARED IMPORTED) + set_target_properties(ShaderCompiler::dxil PROPERTIES + IMPORTED_LOCATION "${DXC_VALIDATOR_RUNTIME}") + + add_library(ShaderCompiler::dxcompiler SHARED IMPORTED) + set_target_properties(ShaderCompiler::dxcompiler PROPERTIES + IMPORTED_IMPLIB "dxcompiler.lib" + IMPORTED_LOCATION "${DXC_COMPILER_RUNTIME}" + IMPORTED_LINK_DEPENDENT_LIBRARIES ShaderCompiler::dxil) + target_link_libraries(ShaderCompiler + PRIVATE ShaderCompiler::dxcompiler) + endif() endif() # TODO: remove this once the #define in ShaderCompilerCommon gets split into separate compilation units diff --git a/Plugins/ShaderTool/CMakeLists.txt b/Plugins/ShaderTool/CMakeLists.txt index ef2741d2dd..39a79a318b 100644 --- a/Plugins/ShaderTool/CMakeLists.txt +++ b/Plugins/ShaderTool/CMakeLists.txt @@ -10,5 +10,10 @@ target_link_libraries(ShaderTool PRIVATE ShaderCacheInternal PRIVATE ShaderCache) +# See https://gitlab.kitware.com/cmake/cmake/-/issues/23543 +# If we can set minimum required to 3.26+, then we can use the `copy -t` syntax instead. +add_custom_command(TARGET ShaderTool POST_BUILD + COMMAND ${CMAKE_COMMAND} -E $>,copy,true> $ $ COMMAND_EXPAND_LISTS) + set_property(TARGET ShaderTool PROPERTY FOLDER Plugins) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) diff --git a/Polyfills/Canvas/Source/Canvas.cpp b/Polyfills/Canvas/Source/Canvas.cpp index 3b78e41347..e08b443d16 100644 --- a/Polyfills/Canvas/Source/Canvas.cpp +++ b/Polyfills/Canvas/Source/Canvas.cpp @@ -190,17 +190,17 @@ namespace Babylon::Polyfills::Internal bgfx::createTexture2D(m_width, m_height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT, mem), bgfx::createTexture2D(m_width, m_height, false, 1, bgfx::TextureFormat::D24S8, BGFX_TEXTURE_RT)}; - // See NativeEngine::CreateFrameBuffer: bgfx validation now asserts when BGFX_RESOLVE_AUTO_GEN_MIPS is used + // See NativeEngine::CreateFrameBuffer: bgfx validation now asserts when BGFX_ATTACHMENT_AUTO_GEN_MIPS is used // with a texture whose format doesn't have BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN. Gate the color attachment - // on the capability and pass BGFX_RESOLVE_NONE for the depth attachment (depth formats never support autogen). + // on the capability and pass BGFX_ATTACHMENT_NONE for the depth attachment (depth formats never support autogen). const bgfx::Caps* caps = bgfx::getCaps(); const uint8_t colorResolve = 0 != (caps->formats[bgfx::TextureFormat::RGBA8] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN) - ? BGFX_RESOLVE_AUTO_GEN_MIPS - : BGFX_RESOLVE_NONE; + ? BGFX_ATTACHMENT_AUTO_GEN_MIPS + : BGFX_ATTACHMENT_NONE; std::array attachments{}; attachments[0].init(textures[0], bgfx::Access::Write, 0, 1, 0, colorResolve); - attachments[1].init(textures[1], bgfx::Access::Write, 0, 1, 0, BGFX_RESOLVE_NONE); + attachments[1].init(textures[1], bgfx::Access::Write, 0, 1, 0, BGFX_ATTACHMENT_NONE); auto handle = bgfx::createFrameBuffer(static_cast(attachments.size()), attachments.data(), true); if (!bgfx::isValid(handle)) { diff --git a/Polyfills/Canvas/Source/FrameBufferPool.cpp b/Polyfills/Canvas/Source/FrameBufferPool.cpp index d8ea6f720b..d7bfec95bc 100644 --- a/Polyfills/Canvas/Source/FrameBufferPool.cpp +++ b/Polyfills/Canvas/Source/FrameBufferPool.cpp @@ -54,17 +54,17 @@ namespace Babylon::Polyfills bgfx::createTexture2D(m_width, m_height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT | BGFX_SAMPLER_U_BORDER | BGFX_SAMPLER_V_BORDER | BGFX_SAMPLER_BORDER_COLOR(0), mem), bgfx::createTexture2D(m_width, m_height, false, 1, bgfx::TextureFormat::D24S8, BGFX_TEXTURE_RT | BGFX_SAMPLER_U_BORDER | BGFX_SAMPLER_V_BORDER | BGFX_SAMPLER_BORDER_COLOR(0))}; - // See NativeEngine::CreateFrameBuffer: bgfx validation now asserts when BGFX_RESOLVE_AUTO_GEN_MIPS is used + // See NativeEngine::CreateFrameBuffer: bgfx validation now asserts when BGFX_ATTACHMENT_AUTO_GEN_MIPS is used // with a texture whose format doesn't have BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN. Gate the color attachment - // on the capability and pass BGFX_RESOLVE_NONE for the depth attachment (depth formats never support autogen). + // on the capability and pass BGFX_ATTACHMENT_NONE for the depth attachment (depth formats never support autogen). const bgfx::Caps* caps = bgfx::getCaps(); const uint8_t colorResolve = 0 != (caps->formats[bgfx::TextureFormat::RGBA8] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN) - ? BGFX_RESOLVE_AUTO_GEN_MIPS - : BGFX_RESOLVE_NONE; + ? BGFX_ATTACHMENT_AUTO_GEN_MIPS + : BGFX_ATTACHMENT_NONE; std::array attachments{}; attachments[0].init(textures[0], bgfx::Access::Write, 0, 1, 0, colorResolve); - attachments[1].init(textures[1], bgfx::Access::Write, 0, 1, 0, BGFX_RESOLVE_NONE); + attachments[1].init(textures[1], bgfx::Access::Write, 0, 1, 0, BGFX_ATTACHMENT_NONE); TextBuffer = bgfx::createFrameBuffer(static_cast(attachments.size()), attachments.data(), true); if (!bgfx::isValid(TextBuffer))