From 9746554af2efafabb73ce520496ae9df2c138f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Mon, 14 Sep 2026 15:26:33 -0700 Subject: [PATCH 1/8] Update bgfx and migrate graphics to explicit swap chains Pin the merged bgfx.cmake revision with current upstream build scripts, EGL headless fixes, and the D3D12 explicit-swap-chain MSAA correction. Own window framebuffers independently of the graphics device, retain and import D3D11 back-buffer views, and coalesce capture and screenshot readbacks. Update attachment flags, baseline instancing support, and camera/XR texture imports for the current bgfx API. Deploy the D3D12 shader runtime for native consumers, remove obsolete miniz target references, and preserve disabled video support through the new CMake option. Add swap-chain, capture, external-view, shader, and D3D12 debug-layer regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 54aaa1e1-b4b3-46e7-9de4-5a56add4ac42 --- Apps/ModuleLoadTest/CMakeLists.txt | 4 + Apps/Playground/CMakeLists.txt | 4 + Apps/UnitTests/CMakeLists.txt | 10 +- Apps/UnitTests/Source/Tests.BgfxCallback.cpp | 65 +++ Apps/UnitTests/Source/Tests.Device.D3D11.cpp | 302 +++++++++- .../Source/Tests.Device.SwapChain.cpp | 206 +++++++ .../Source/Tests.ShaderCompilation.cpp | 25 + CMakeLists.txt | 2 +- Core/Graphics/CMakeLists.txt | 8 +- .../Include/Shared/Babylon/Graphics/Device.h | 4 +- .../Babylon/Graphics/BgfxCallback.h | 3 + .../Babylon/Graphics/D3D11Format.h | 206 +++++++ .../Babylon/Graphics/DeviceContext.h | 1 + Core/Graphics/Source/BgfxCallback.cpp | 32 +- Core/Graphics/Source/DeviceContext.cpp | 5 + Core/Graphics/Source/DeviceImpl.cpp | 224 ++++++-- Core/Graphics/Source/DeviceImpl.h | 31 +- Core/Graphics/Source/DeviceImpl_Android.cpp | 10 +- Core/Graphics/Source/DeviceImpl_Unix.cpp | 8 +- Core/Graphics/Source/DeviceImpl_Win32.cpp | 6 +- Core/Graphics/Source/DeviceImpl_WinRT.cpp | 9 +- Core/Graphics/Source/DeviceImpl_iOS.mm | 6 +- Core/Graphics/Source/DeviceImpl_macOS.mm | 6 +- Core/Graphics/Source/DeviceImpl_visionOS.mm | 6 +- .../Source/ExternalBackBufferD3D11.cpp | 544 ++++++++++++++++++ .../Graphics/Source/ExternalBackBufferD3D11.h | 32 ++ Core/Graphics/Source/FrameBuffer.cpp | 6 +- Dependencies/CMakeLists.txt | 5 +- Documentation/Components.md | 7 + Install/Install.cmake | 2 +- .../Source/ExternalTexture_D3D11.cpp | 172 +----- .../Source/Android/CameraDevice.cpp | 27 +- .../NativeCamera/Source/Apple/CameraDevice.mm | 48 +- Plugins/NativeCamera/Source/CameraDevice.h | 7 +- Plugins/NativeCamera/Source/MediaStream.cpp | 4 +- Plugins/NativeCamera/Source/MediaStream.h | 2 +- Plugins/NativeCamera/Source/NativeCamera.cpp | 4 +- Plugins/NativeCamera/Source/NativeVideo.cpp | 4 +- Plugins/NativeCamera/Source/NativeVideo.h | 2 +- .../NativeCamera/Source/Unix/CameraDevice.cpp | 2 +- .../Source/Win32/CameraDevice.cpp | 2 +- .../Source/WinRT/CameraDevice.cpp | 2 +- Plugins/NativeEngine/CMakeLists.txt | 1 - Plugins/NativeEngine/Source/NativeEngine.cpp | 6 +- Plugins/NativeEngine/Source/VertexArray.cpp | 10 +- Plugins/NativeXr/Source/NativeXrImpl.cpp | 49 +- Plugins/ShaderCompiler/CMakeLists.txt | 50 ++ Plugins/ShaderTool/CMakeLists.txt | 2 + Polyfills/Canvas/Source/Canvas.cpp | 10 +- Polyfills/Canvas/Source/FrameBufferPool.cpp | 10 +- 50 files changed, 1826 insertions(+), 367 deletions(-) create mode 100644 Apps/UnitTests/Source/Tests.BgfxCallback.cpp create mode 100644 Apps/UnitTests/Source/Tests.Device.SwapChain.cpp create mode 100644 Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h create mode 100644 Core/Graphics/Source/ExternalBackBufferD3D11.cpp create mode 100644 Core/Graphics/Source/ExternalBackBufferD3D11.h diff --git a/Apps/ModuleLoadTest/CMakeLists.txt b/Apps/ModuleLoadTest/CMakeLists.txt index 1ce26bd8c3..6287b36419 100644 --- a/Apps/ModuleLoadTest/CMakeLists.txt +++ b/Apps/ModuleLoadTest/CMakeLists.txt @@ -37,6 +37,10 @@ target_link_libraries(ModuleLoadTest PRIVATE XMLHttpRequest ${ADDITIONAL_LIBRARIES}) +if(BABYLON_NATIVE_PLUGIN_SHADERCOMPILER) + deploy_shader_compiler_runtime(ModuleLoadTest) +endif() + add_test(NAME ModuleLoadTest COMMAND ModuleLoadTest CONFIGURATIONS Release RelWithDebInfo) # See https://gitlab.kitware.com/cmake/cmake/-/issues/23543 diff --git a/Apps/Playground/CMakeLists.txt b/Apps/Playground/CMakeLists.txt index 643de62fde..dbd8a1bf32 100644 --- a/Apps/Playground/CMakeLists.txt +++ b/Apps/Playground/CMakeLists.txt @@ -161,6 +161,10 @@ target_link_libraries(Playground ${ADDITIONAL_LIBRARIES} ${BABYLON_NATIVE_PLAYGROUND_EXTENSION_LIBRARIES}) +if(BABYLON_NATIVE_PLUGIN_SHADERCOMPILER) + deploy_shader_compiler_runtime(Playground) +endif() + # 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 Playground POST_BUILD diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index 3475fffe73..6c0060af91 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" @@ -99,6 +101,12 @@ 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) + deploy_shader_compiler_runtime(UnitTests) +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.cpp b/Apps/UnitTests/Source/Tests.Device.D3D11.cpp index d91427eebf..e0e581741c 100644 --- a/Apps/UnitTests/Source/Tests.Device.D3D11.cpp +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.cpp @@ -1,11 +1,23 @@ #include +#include #include +#include +#include #include "Helpers.h" #include +#include +#include +#include +#include +#include +#include +#include +#include + extern Babylon::Graphics::Configuration g_deviceConfig; namespace @@ -33,72 +45,302 @@ namespace winrt::com_ptr View; }; - RenderTargetTexture CreateTestRenderTargetTexture(ID3D11Device* device, uint32_t width, uint32_t height) + 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 = DXGI_FORMAT_R8G8B8A8_UNORM; - desc.SampleDesc.Count = 1; - desc.SampleDesc.Quality = 0; + desc.Format = format; + desc.SampleDesc.Count = samples; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_RENDER_TARGET; - desc.CPUAccessFlags = 0; - desc.MiscFlags = 0; 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 = D3D11_RTV_DIMENSION_TEXTURE2D; - rtvDesc.Texture2D.MipSlice = 0; + 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}; + } + + 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, BackBuffer) { winrt::com_ptr d3dDevice = CreateDevice(); + auto first = CreateTestRenderTargetTexture( + d3dDevice.get(), + 64, + 48, + 1, + DXGI_FORMAT_B8G8R8A8_UNORM); - constexpr SIZE dimensions[] = { - {1600, 900}, - {1280, 720}, - {1600, 900}, - {640, 360}, - {640, 360}, - {1280, 720}, - }; + Babylon::Graphics::Configuration config{}; + config.Device = d3dDevice.get(); + config.BackBufferColor = first.View.get(); + config.Width = 64; + config.Height = 48; - std::vector renderTargetTextures; - for (size_t i = 0; i < std::size(dimensions); ++i) - { - renderTargetTextures.push_back(CreateTestRenderTargetTexture(d3dDevice.get(), dimensions[i].cx, dimensions[i].cy)); - } + 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 = renderTargetTextures[0].View.get(); - config.Width = dimensions[0].cx; - config.Height = dimensions[0].cy; + 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); - for (size_t i = 1; i < std::size(dimensions); ++i) + 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) { - device.StartRenderingCurrentFrame(); + GTEST_SKIP() << "D3D11 WARP does not support 4x MSAA for RGBA8."; + } - device.UpdateBackBuffer(renderTargetTextures[i].View.get()); - device.UpdateSize(dimensions[i].cx, dimensions[i].cy); + 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; - device.FinishRenderingCurrentFrame(); - } + Babylon::Graphics::Device device{config}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); } // Verifies that UpdateDevice throws when called while rendering is enabled. diff --git a/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp b/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp new file mode 100644 index 0000000000..af308f7a65 --- /dev/null +++ b/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp @@ -0,0 +1,206 @@ +#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) + { + 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{"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, 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.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..f399ef94c4 100644 --- a/Core/Graphics/CMakeLists.txt +++ b/Core/Graphics/CMakeLists.txt @@ -24,6 +24,13 @@ if(GRAPHICS_API STREQUAL "OpenGL") "Include/RendererType/OpenGL/Babylon/Graphics/GL/Texture.h") endif() +if(GRAPHICS_API STREQUAL "D3D11") + list(APPEND SOURCES + "InternalInclude/Babylon/Graphics/D3D11Format.h" + "Source/ExternalBackBufferD3D11.h" + "Source/ExternalBackBufferD3D11.cpp") +endif() + add_library(Graphics ${SOURCES}) warnings_as_errors(Graphics) @@ -43,7 +50,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..00ff65bf0f 100644 --- a/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h +++ b/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h @@ -35,7 +35,7 @@ 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. BackBufferDepthStencilT BackBufferDepthStencil{}; #endif @@ -96,6 +96,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 +112,7 @@ 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. 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/D3D11Format.h b/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h new file mode 100644 index 0000000000..a3f201e63e --- /dev/null +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h @@ -0,0 +1,206 @@ +#pragma once + +#include +#include + +#include +#include +#include + +// clang-format off + +// 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. +#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) + +namespace Babylon::Graphics::D3D11TextureFormats +{ + struct TextureFormatInfo + { + DXGI_FORMAT Linear; + DXGI_FORMAT Srgb; + }; + + inline constexpr TextureFormatInfo TextureFormats[] = { + {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 + {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // EACR11S + {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // EACRG11 + {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // EACRG11S + {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(std::size(TextureFormats) == bgfx::TextureFormat::Count); + + struct BgfxTextureFormat + { + bgfx::TextureFormat::Enum Format; + bool Srgb; + }; + + inline std::optional TryGetBgfxTextureFormat(DXGI_FORMAT format) + { + if (format == DXGI_FORMAT_UNKNOWN) + { + return std::nullopt; + } + + for (size_t index = 0; index < std::size(TextureFormats); ++index) + { + if (TextureFormats[index].Linear == format) + { + return BgfxTextureFormat{static_cast(index), false}; + } + if (TextureFormats[index].Srgb == format) + { + return BgfxTextureFormat{static_cast(index), true}; + } + } + + return std::nullopt; + } + + inline 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 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/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..734d48b164 100644 --- a/Core/Graphics/Source/DeviceImpl.cpp +++ b/Core/Graphics/Source/DeviceImpl.cpp @@ -5,10 +5,16 @@ #include #include #include +#include +#include #include #include #include +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT +#include "ExternalBackBufferD3D11.h" +#endif + #if defined(__APPLE__) #include #endif @@ -101,11 +107,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 +120,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 +143,22 @@ namespace Babylon::Graphics return m_bgfxId; } + bgfx::FrameBufferHandle DeviceImpl::GetBackBufferHandle() const + { +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + if (m_externalBackBuffer && bgfx::isValid(m_externalBackBuffer->GetFrameBuffer())) + { + return m_externalBackBuffer->GetFrameBuffer(); + } +#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 +186,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 +194,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 +216,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 +225,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 +267,41 @@ 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 = {}; + } + }); + + 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. + 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 +312,8 @@ namespace Babylon::Graphics m_renderResetCallback(); } } + m_state.Bgfx.Dirty = false; + ready = true; } } @@ -295,6 +339,7 @@ namespace Babylon::Graphics m_cancellationSource->cancel(); + DestroyBackBuffer(); bgfx::shutdown(); m_state.Bgfx.Initialized = false; m_bgfxId++; @@ -503,16 +548,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 +708,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 +723,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 +735,105 @@ 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 + m_externalBackBuffer.reset(); +#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 || 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) + { + m_externalBackBuffer = std::make_unique( + m_state.BackBufferColor.get(), m_state.BackBufferDepthStencil.get(), swapChain); + if (bgfx::isValid(m_externalBackBuffer->GetFrameBuffer())) + { + return; + } + swapChain.depth = m_externalBackBuffer->GetDepthTexture(); + } + } +#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 (m_externalBackBuffer && bgfx::isValid(m_externalBackBuffer->GetFrameBuffer())) + { + 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 +844,21 @@ 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) + { + m_externalBackBuffer->ReadPixels([this](const auto& data) { + m_bgfxCallback.CompleteScreenShot(data); + }); + } +#endif + // Process read texture requests. while (!m_readTextureRequests.empty() && m_readTextureRequests.front().first <= frameNumber) { @@ -745,15 +874,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..7530ede181 100644 --- a/Core/Graphics/Source/DeviceImpl.h +++ b/Core/Graphics/Source/DeviceImpl.h @@ -20,8 +20,16 @@ #include #include +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT +#include +#endif + namespace Babylon::Graphics { +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + class ExternalBackBufferD3D11; +#endif + class DeviceImpl { public: @@ -71,6 +79,7 @@ namespace Babylon::Graphics PlatformInfo GetPlatformInfo() const; uintptr_t GetId() const; + bgfx::FrameBufferHandle GetBackBufferHandle() const; /* ********** END DEVICE CONTRACT ********** */ @@ -130,8 +139,8 @@ 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 @@ -141,7 +150,9 @@ namespace Babylon::Graphics void UpdateBgfxState(); void UpdateBgfxResolution(); - void RequestScreenShots(); + void UpdateBackBufferState(); + void DestroyBackBuffer(); + bool RequestScreenShots(); void Frame(); void PerformMidFrameViewFlush(); void CaptureCallback(const BgfxCallback::CaptureData&); @@ -181,18 +192,30 @@ namespace Babylon::Graphics std::optional m_cancellationSource{}; + bgfx::FrameBufferHandle m_windowFrameBuffer{bgfx::kInvalidHandle}; + void* m_windowHandle{}; + void* m_displayHandle{}; +#ifdef GRAPHICS_BACK_BUFFER_SUPPORT + std::unique_ptr 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_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_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/ExternalBackBufferD3D11.cpp b/Core/Graphics/Source/ExternalBackBufferD3D11.cpp new file mode 100644 index 0000000000..c19cc9f792 --- /dev/null +++ b/Core/Graphics/Source/ExternalBackBufferD3D11.cpp @@ -0,0 +1,544 @@ +#include "ExternalBackBufferD3D11.h" + +#include + +#include +#include + +#include +#include +#include +#include + +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 +{ + class ExternalBackBufferD3D11::Impl + { + public: + Impl(BackBufferColorT color, BackBufferDepthStencilT depth, const bgfx::SwapChain& descriptor) + { + 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) + { + Destroy(); + } + }); + + std::optional colorInfo; + if (color != nullptr) + { + m_colorView.copy_from(color); + colorInfo = GetColorInfo(color); + ValidateDevice(*colorInfo, bgfxDevice); + m_colorTexture = colorInfo->Texture; + m_colorInfo = *colorInfo; + m_colorTextureHandle = ImportTexture(*colorInfo); + } + + std::optional depthInfo; + if (depth != nullptr) + { + m_depthView.copy_from(depth); + depthInfo = GetDepthInfo(depth); + ValidateDevice(*depthInfo, bgfxDevice); + m_depthTexture = depthInfo->Texture; + m_depthTextureHandle = 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_depthTexture, + m_depthView); + m_depthTextureHandle = ImportTexture(*depthInfo); + } + + std::array attachments{}; + uint8_t attachmentCount{}; + if (colorInfo) + { + attachments[attachmentCount++] = MakeAttachment(m_colorTextureHandle, *colorInfo); + } + if (depthInfo && (colorInfo || descriptor.nwh == nullptr)) + { + attachments[attachmentCount++] = MakeAttachment(m_depthTextureHandle, *depthInfo); + } + + if (attachmentCount != 0) + { + m_frameBuffer = bgfx::createFrameBuffer(attachmentCount, attachments.data(), false); + if (!bgfx::isValid(m_frameBuffer)) + { + throw std::runtime_error{"Failed to create D3D11 external back buffer framebuffer."}; + } + } + + cleanupNeeded = false; + } + + ~Impl() + { + Destroy(); + } + + bgfx::FrameBufferHandle GetFrameBuffer() const + { + return m_frameBuffer; + } + + bgfx::TextureHandle GetDepthTexture() const + { + return m_depthTextureHandle; + } + + void ReadPixels(const std::function& callback) const + { + if (!m_colorInfo || !m_colorTexture) + { + throw std::runtime_error{"Cannot capture a D3D11 external back buffer without a color view."}; + } + + const bgfx::TextureFormat::Enum captureFormat = GetCaptureFormat(m_colorInfo->ViewFormat); + + winrt::com_ptr device; + m_colorTexture->GetDevice(device.put()); + winrt::com_ptr context; + device->GetImmediateContext(context.put()); + + D3D11_TEXTURE2D_DESC copyDesc{}; + copyDesc.Width = m_colorInfo->Width; + copyDesc.Height = m_colorInfo->Height; + copyDesc.MipLevels = 1; + copyDesc.ArraySize = 1; + copyDesc.Format = m_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( + m_colorInfo->Mip, + m_colorInfo->FirstLayer, + m_colorInfo->TextureDesc.MipLevels); + + if (m_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_colorTexture.get(), + sourceSubresource, + m_colorInfo->ViewFormat); + context->CopyResource(staging.get(), resolved.get()); + } + else + { + context->CopySubresourceRegion( + staging.get(), + 0, + 0, + 0, + 0, + m_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); }); + + callback(BgfxCallback::CaptureData{ + m_colorInfo->Width, + m_colorInfo->Height, + mapped.RowPitch, + captureFormat, + false, + mapped.pData, + mapped.RowPitch * m_colorInfo->Height}); + } + + private: + void Destroy() + { + if (bgfx::isValid(m_frameBuffer)) + { + bgfx::destroy(m_frameBuffer); + m_frameBuffer = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(m_colorTextureHandle)) + { + bgfx::destroy(m_colorTextureHandle); + m_colorTextureHandle = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(m_depthTextureHandle)) + { + bgfx::destroy(m_depthTextureHandle); + m_depthTextureHandle = BGFX_INVALID_HANDLE; + } + } + + winrt::com_ptr m_colorView; + winrt::com_ptr m_depthView; + winrt::com_ptr m_colorTexture; + winrt::com_ptr m_depthTexture; + std::optional m_colorInfo; + bgfx::FrameBufferHandle m_frameBuffer{BGFX_INVALID_HANDLE}; + bgfx::TextureHandle m_colorTextureHandle{BGFX_INVALID_HANDLE}; + bgfx::TextureHandle m_depthTextureHandle{BGFX_INVALID_HANDLE}; + }; + + ExternalBackBufferD3D11::ExternalBackBufferD3D11( + BackBufferColorT color, + BackBufferDepthStencilT depth, + const bgfx::SwapChain& descriptor) + : m_impl{std::make_unique(color, depth, descriptor)} + { + } + + ExternalBackBufferD3D11::~ExternalBackBufferD3D11() = default; + + bgfx::FrameBufferHandle ExternalBackBufferD3D11::GetFrameBuffer() const + { + return m_impl->GetFrameBuffer(); + } + + bgfx::TextureHandle ExternalBackBufferD3D11::GetDepthTexture() const + { + return m_impl->GetDepthTexture(); + } + + void ExternalBackBufferD3D11::ReadPixels( + const std::function& callback) const + { + m_impl->ReadPixels(callback); + } +} diff --git a/Core/Graphics/Source/ExternalBackBufferD3D11.h b/Core/Graphics/Source/ExternalBackBufferD3D11.h new file mode 100644 index 0000000000..4b7f692365 --- /dev/null +++ b/Core/Graphics/Source/ExternalBackBufferD3D11.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace Babylon::Graphics +{ + class ExternalBackBufferD3D11 final + { + public: + ExternalBackBufferD3D11(BackBufferColorT color, BackBufferDepthStencilT depth, const bgfx::SwapChain& descriptor); + ~ExternalBackBufferD3D11(); + + ExternalBackBufferD3D11(const ExternalBackBufferD3D11&) = delete; + ExternalBackBufferD3D11& operator=(const ExternalBackBufferD3D11&) = delete; + ExternalBackBufferD3D11(ExternalBackBufferD3D11&&) = delete; + ExternalBackBufferD3D11& operator=(ExternalBackBufferD3D11&&) = delete; + + bgfx::FrameBufferHandle GetFrameBuffer() const; + bgfx::TextureHandle GetDepthTexture() const; + void ReadPixels(const std::function& callback) const; + + private: + class Impl; + std::unique_ptr m_impl; + }; +} diff --git a/Core/Graphics/Source/FrameBuffer.cpp b/Core/Graphics/Source/FrameBuffer.cpp index e2af51a3d4..d5e3da16bd 100644 --- a/Core/Graphics/Source/FrameBuffer.cpp +++ b/Core/Graphics/Source/FrameBuffer.cpp @@ -51,7 +51,7 @@ namespace Babylon::Graphics bgfx::FrameBufferHandle FrameBuffer::Handle() const { - return m_handle; + return m_defaultBackBuffer ? m_deviceContext.GetBackBufferHandle() : m_handle; } uint16_t FrameBuffer::Width() const @@ -86,7 +86,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 +222,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/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..49d22c0c90 100644 --- a/Documentation/Components.md +++ b/Documentation/Components.md @@ -39,6 +39,13 @@ 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. + ### glslang [glslang](https://github.com/KhronosGroup/glslang) is the reference compiler diff --git a/Install/Install.cmake b/Install/Install.cmake index d0fb5b1ddd..499fbf9527 100644 --- a/Install/Install.cmake +++ b/Install/Install.cmake @@ -43,7 +43,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..dad22c0cb5 100644 --- a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp +++ b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp @@ -1,166 +1,12 @@ #include +#include #include #include #include -#include #include #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) @@ -211,19 +57,13 @@ namespace Babylon::Plugins } } - DXGI_FORMAT targetFormat = overrideFormat.has_value() ? overrideFormat.value() : desc.Format; - for (int i = 0; i < BX_COUNTOF(s_textureFormat); ++i) + const DXGI_FORMAT targetFormat = overrideFormat.value_or(desc.Format); + 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..02e419c586 100644 --- a/Plugins/NativeCamera/Source/Apple/CameraDevice.mm +++ b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -236,8 +237,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 +418,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,11 +579,11 @@ 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. + // Hook into AfterRender to copy over the texture without overlapping bgfx's frame encoding. // 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}] { + arcana::make_task(m_impl->deviceContext->AfterRenderScheduler(), *m_impl->cancellationSource, [this, texture{&texture}, cancellationSource{m_impl->cancellationSource}] { id textureY{}; id textureCbCr{}; int64_t width{0}; @@ -612,11 +614,10 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], 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) + // 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->refreshBgfxHandle = true; - m_impl->textureHandle = textureHandle; + m_impl->refreshBgfxTexture = true; } // Recreate the output texture when the camera dimensions change. @@ -627,16 +628,25 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], 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; + m_impl->refreshBgfxTexture = true; } - else if (m_impl->refreshBgfxHandle) + + if (m_impl->refreshBgfxTexture && m_impl->textureRGBA != nil) { - m_impl->refreshBgfxHandle = bgfx::overrideInternal(textureHandle, reinterpret_cast(m_impl->textureRGBA)) == 0; + texture->Create2D( + static_cast(width), + static_cast(height), + texture->HasMips(), + texture->NumLayers(), + texture->Format(), + texture->Flags(), + reinterpret_cast(m_impl->textureRGBA)); + m_impl->bgfxTexture = texture; + m_impl->bgfxTextureHandle = texture->Handle(); + m_impl->refreshBgfxTexture = false; } - if (textureY != nil && textureCbCr != nil && m_impl->textureRGBA != nil && !m_impl->refreshBgfxHandle) + if (textureY != nil && textureCbCr != nil && m_impl->textureRGBA != nil) { m_impl->currentCommandBuffer = [m_impl->commandQueue commandBuffer]; m_impl->currentCommandBuffer.label = @"NativeCameraCommandBuffer"; @@ -825,8 +835,8 @@ 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. + // Capturing textureRGBA, textureDelegate, and textureCache maintains the imported texture's + // lifetime until after the render pass. Otherwise bgfx could access a released Metal texture. 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] { diff --git a/Plugins/NativeCamera/Source/CameraDevice.h b/Plugins/NativeCamera/Source/CameraDevice.h index d644623b67..4d8686a628 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,7 @@ namespace Babylon::Plugins arcana::task OpenAsync(const CameraTrack& track); void Close(); - CameraDimensions UpdateCameraTexture(bgfx::TextureHandle textureHandle); + 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..f026e61e9c 100644 --- a/Plugins/ShaderCompiler/CMakeLists.txt +++ b/Plugins/ShaderCompiler/CMakeLists.txt @@ -45,6 +45,56 @@ if(GRAPHICS_API STREQUAL "D3D11") PRIVATE "d3dcompiler.lib") endif() +function(deploy_shader_compiler_runtime TARGET_NAME) + if(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) + + 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) + get_property(DXC_RUNTIME_WARNING_EMITTED GLOBAL PROPERTY BABYLON_DXC_RUNTIME_WARNING_EMITTED) + if(NOT DXC_RUNTIME_WARNING_EMITTED) + 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.") + set_property(GLOBAL PROPERTY BABYLON_DXC_RUNTIME_WARNING_EMITTED TRUE) + endif() + return() + endif() + + set(DXC_RUNTIME_DIR "${BGFX_DIR}/tools/bin/windows") + set(DXC_RUNTIME_FILES + "${DXC_RUNTIME_DIR}/dxcompiler.dll" + "${DXC_RUNTIME_DIR}/dxil.dll") + + foreach(DXC_RUNTIME_FILE IN LISTS DXC_RUNTIME_FILES) + if(NOT EXISTS "${DXC_RUNTIME_FILE}") + message(FATAL_ERROR "D3D12 shader compilation requires ${DXC_RUNTIME_FILE}") + endif() + endforeach() + + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${DXC_RUNTIME_FILES} + "$" + COMMENT "Copying DirectX Shader Compiler runtime to ${TARGET_NAME}") + endif() +endfunction() + # TODO: remove this once the #define in ShaderCompilerCommon gets split into separate compilation units target_compile_definitions(ShaderCompiler PRIVATE $) diff --git a/Plugins/ShaderTool/CMakeLists.txt b/Plugins/ShaderTool/CMakeLists.txt index ef2741d2dd..a26cd655e6 100644 --- a/Plugins/ShaderTool/CMakeLists.txt +++ b/Plugins/ShaderTool/CMakeLists.txt @@ -10,5 +10,7 @@ target_link_libraries(ShaderTool PRIVATE ShaderCacheInternal PRIVATE ShaderCache) +deploy_shader_compiler_runtime(ShaderTool) + 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)) From fa645b152fb00a375727d28eceaa48c2d261ec5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Mon, 14 Sep 2026 16:04:40 -0700 Subject: [PATCH 2/8] Fix installed D3D11 test coverage and isolate ASTC format constants Keep the installed SDK smoke test limited to public headers and move internal external-backbuffer regressions into a UnitTests-only source. Replace shared-header ASTC macros with namespaced typed constants and clarify renderer selection for the headless bootstrap. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 54aaa1e1-b4b3-46e7-9de4-5a56add4ac42 --- Apps/UnitTests/CMakeLists.txt | 3 +- .../Tests.Device.D3D11.ExternalBackBuffer.cpp | 342 ++++++++++++++++++ Apps/UnitTests/Source/Tests.Device.D3D11.cpp | 300 ++------------- .../Babylon/Graphics/D3D11Format.h | 90 ++--- Core/Graphics/Source/DeviceImpl.cpp | 2 + 5 files changed, 422 insertions(+), 315 deletions(-) create mode 100644 Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index 6c0060af91..ad89700802 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -57,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) 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..3da7e0077c --- /dev/null +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp @@ -0,0 +1,342 @@ +#include + +#include +#include +#include +#include + +#include "Helpers.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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}; + } + + 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); +} diff --git a/Apps/UnitTests/Source/Tests.Device.D3D11.cpp b/Apps/UnitTests/Source/Tests.Device.D3D11.cpp index e0e581741c..59e6b61941 100644 --- a/Apps/UnitTests/Source/Tests.Device.D3D11.cpp +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.cpp @@ -1,21 +1,13 @@ #include -#include #include -#include -#include #include "Helpers.h" #include -#include -#include -#include -#include -#include +#include #include -#include #include extern Babylon::Graphics::Configuration g_deviceConfig; @@ -45,302 +37,72 @@ namespace 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) + RenderTargetTexture CreateTestRenderTargetTexture(ID3D11Device* device, uint32_t width, uint32_t height) { D3D11_TEXTURE2D_DESC desc{}; desc.Width = width; desc.Height = height; desc.MipLevels = 1; desc.ArraySize = 1; - desc.Format = format; - desc.SampleDesc.Count = samples; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_RENDER_TARGET; + desc.CPUAccessFlags = 0; + desc.MiscFlags = 0; 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; + rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + rtvDesc.Texture2D.MipSlice = 0; 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}; - } - - 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, BackBuffer) { 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()); + constexpr SIZE dimensions[] = { + {1600, 900}, + {1280, 720}, + {1600, 900}, + {640, 360}, + {640, 360}, + {1280, 720}, + }; - 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}); + std::vector renderTargetTextures; + for (size_t i = 0; i < std::size(dimensions); ++i) + { + renderTargetTextures.push_back(CreateTestRenderTargetTexture(d3dDevice.get(), dimensions[i].cx, dimensions[i].cy)); + } Babylon::Graphics::Configuration config{}; config.Device = d3dDevice.get(); - config.BackBufferColor = color.View.get(); - config.BackBufferDepthStencil = depth.View.get(); - config.Width = 16; - config.Height = 8; + config.BackBufferColor = renderTargetTextures[0].View.get(); + config.Width = dimensions[0].cx; + config.Height = dimensions[0].cy; 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) + for (size_t i = 1; i < std::size(dimensions); ++i) { - GTEST_SKIP() << "D3D11 WARP does not support 4x MSAA for RGBA8."; - } + device.StartRenderingCurrentFrame(); - 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; + device.UpdateBackBuffer(renderTargetTextures[i].View.get()); + device.UpdateSize(dimensions[i].cx, dimensions[i].cy); - Babylon::Graphics::Device device{config}; - Babylon::AppRuntime runtime{}; - auto& context = GetContext(device, runtime); - ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); + device.FinishRenderingCurrentFrame(); + } } // Verifies that UpdateDevice throws when called while rendering is enabled. diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h b/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h index a3f201e63e..d523087113 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h @@ -9,39 +9,39 @@ // clang-format off -// 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. -#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) - 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 Astc4x4Unorm{static_cast(134)}; + inline constexpr DXGI_FORMAT Astc4x4UnormSrgb{static_cast(135)}; + inline constexpr DXGI_FORMAT Astc5x4Unorm{static_cast(138)}; + inline constexpr DXGI_FORMAT Astc5x4UnormSrgb{static_cast(139)}; + inline constexpr DXGI_FORMAT Astc5x5Unorm{static_cast(142)}; + inline constexpr DXGI_FORMAT Astc5x5UnormSrgb{static_cast(143)}; + inline constexpr DXGI_FORMAT Astc6x5Unorm{static_cast(146)}; + inline constexpr DXGI_FORMAT Astc6x5UnormSrgb{static_cast(147)}; + inline constexpr DXGI_FORMAT Astc6x6Unorm{static_cast(150)}; + inline constexpr DXGI_FORMAT Astc6x6UnormSrgb{static_cast(151)}; + inline constexpr DXGI_FORMAT Astc8x5Unorm{static_cast(154)}; + inline constexpr DXGI_FORMAT Astc8x5UnormSrgb{static_cast(155)}; + inline constexpr DXGI_FORMAT Astc8x6Unorm{static_cast(158)}; + inline constexpr DXGI_FORMAT Astc8x6UnormSrgb{static_cast(159)}; + inline constexpr DXGI_FORMAT Astc8x8Unorm{static_cast(162)}; + inline constexpr DXGI_FORMAT Astc8x8UnormSrgb{static_cast(163)}; + inline constexpr DXGI_FORMAT Astc10x5Unorm{static_cast(166)}; + inline constexpr DXGI_FORMAT Astc10x5UnormSrgb{static_cast(167)}; + inline constexpr DXGI_FORMAT Astc10x6Unorm{static_cast(170)}; + inline constexpr DXGI_FORMAT Astc10x6UnormSrgb{static_cast(171)}; + inline constexpr DXGI_FORMAT Astc10x8Unorm{static_cast(174)}; + inline constexpr DXGI_FORMAT Astc10x8UnormSrgb{static_cast(175)}; + inline constexpr DXGI_FORMAT Astc10x10Unorm{static_cast(178)}; + inline constexpr DXGI_FORMAT Astc10x10UnormSrgb{static_cast(179)}; + inline constexpr DXGI_FORMAT Astc12x10Unorm{static_cast(182)}; + inline constexpr DXGI_FORMAT Astc12x10UnormSrgb{static_cast(183)}; + inline constexpr DXGI_FORMAT Astc12x12Unorm{static_cast(186)}; + inline constexpr DXGI_FORMAT Astc12x12UnormSrgb{static_cast(187)}; + struct TextureFormatInfo { DXGI_FORMAT Linear; @@ -76,20 +76,20 @@ namespace Babylon::Graphics::D3D11TextureFormats {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 + {Astc4x4Unorm, Astc4x4UnormSrgb}, // ASTC4x4 + {Astc5x4Unorm, Astc5x4UnormSrgb}, // ASTC5x4 + {Astc5x5Unorm, Astc5x5UnormSrgb}, // ASTC5x5 + {Astc6x5Unorm, Astc6x5UnormSrgb}, // ASTC6x5 + {Astc6x6Unorm, Astc6x6UnormSrgb}, // ASTC6x6 + {Astc8x5Unorm, Astc8x5UnormSrgb}, // ASTC8x5 + {Astc8x6Unorm, Astc8x6UnormSrgb}, // ASTC8x6 + {Astc8x8Unorm, Astc8x8UnormSrgb}, // ASTC8x8 + {Astc10x5Unorm, Astc10x5UnormSrgb}, // ASTC10x5 + {Astc10x6Unorm, Astc10x6UnormSrgb}, // ASTC10x6 + {Astc10x8Unorm, Astc10x8UnormSrgb}, // ASTC10x8 + {Astc10x10Unorm, Astc10x10UnormSrgb}, // ASTC10x10 + {Astc12x10Unorm, Astc12x10UnormSrgb}, // ASTC12x10 + {Astc12x12Unorm, Astc12x12UnormSrgb}, // ASTC12x12 {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // Unknown {DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_UNKNOWN}, // R1 {DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_UNKNOWN}, // A8 diff --git a/Core/Graphics/Source/DeviceImpl.cpp b/Core/Graphics/Source/DeviceImpl.cpp index 734d48b164..eef52b5318 100644 --- a/Core/Graphics/Source/DeviceImpl.cpp +++ b/Core/Graphics/Source/DeviceImpl.cpp @@ -287,10 +287,12 @@ namespace Babylon::Graphics } }); + // 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; From 88fe26a31a8a001c5457475c86672b91d7b4db85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Mon, 14 Sep 2026 17:22:05 -0700 Subject: [PATCH 3/8] Preserve explicit framebuffers and guard deferred camera texture updates Distinguish device-window routing from default framebuffer semantics so XR targets never fall back to the window, including after disposal. Replace deferred Apple camera wrapper pointers with generation-checked shared texture state that rejects updates after disposal or destruction. Add framebuffer routing and deferred texture lifetime regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 54aaa1e1-b4b3-46e7-9de4-5a56add4ac42 --- Apps/UnitTests/CMakeLists.txt | 1 + .../Source/Tests.Device.SwapChain.cpp | 38 ++- .../Source/Tests.Texture.DeferredUpdate.cpp | 95 +++++++ .../Babylon/Graphics/FrameBuffer.h | 1 + .../Babylon/Graphics/Texture.h | 70 +++-- Core/Graphics/Source/FrameBuffer.cpp | 4 +- Core/Graphics/Source/Texture.cpp | 256 ++++++++++++++---- Documentation/Components.md | 4 + .../NativeCamera/Source/Apple/CameraDevice.mm | 29 +- 9 files changed, 400 insertions(+), 98 deletions(-) create mode 100644 Apps/UnitTests/Source/Tests.Texture.DeferredUpdate.cpp diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index ad89700802..7093597ddf 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -46,6 +46,7 @@ set(SOURCES "Source/Tests.ShaderCache.cpp" "Source/Tests.ShaderCompilation.cpp" "Source/Tests.ShaderCompilation.FragCoord.cpp" + "Source/Tests.Texture.DeferredUpdate.cpp" "Source/Tests.UniformPadding.cpp" "Source/Helpers.h" "Source/Helpers.${GRAPHICS_API}.${BABYLON_NATIVE_PLATFORM_IMPL_EXT}") diff --git a/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp b/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp index af308f7a65..a08b7aca34 100644 --- a/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp +++ b/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -37,7 +38,8 @@ namespace } std::vector ClearAndCapture( - Babylon::Graphics::Device& device, Babylon::Graphics::DeviceContext& context, uint32_t color) + 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)); }); @@ -46,6 +48,10 @@ namespace 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()) @@ -73,6 +79,36 @@ namespace } } +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, diff --git a/Apps/UnitTests/Source/Tests.Texture.DeferredUpdate.cpp b/Apps/UnitTests/Source/Tests.Texture.DeferredUpdate.cpp new file mode 100644 index 0000000000..4083bcfea2 --- /dev/null +++ b/Apps/UnitTests/Source/Tests.Texture.DeferredUpdate.cpp @@ -0,0 +1,95 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include + +extern Babylon::Graphics::Configuration g_deviceConfig; + +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(); + } + + void InitializeTexture(Babylon::Graphics::Texture& texture) + { + texture.Create2D(4, 4, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE); + } +} + +TEST(Texture, DisposeInvalidatesDeferredUpdate) +{ + Babylon::Graphics::Device device{g_deviceConfig}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + device.StartRenderingCurrentFrame(); + + Babylon::Graphics::Texture texture{context}; + InitializeTexture(texture); + auto update = texture.CreateDeferredUpdate(); + + texture.Dispose(); + auto disposedUpdate = texture.CreateDeferredUpdate(); + + EXPECT_FALSE(update.Matches(disposedUpdate)); + EXPECT_FALSE(update.TryCreate2D(8, 8, 0)); + EXPECT_FALSE(disposedUpdate.TryCreate2D(8, 8, 0)); + EXPECT_FALSE(texture.IsValid()); + + device.FinishRenderingCurrentFrame(); +} + +TEST(Texture, DestructionInvalidatesDeferredUpdate) +{ + Babylon::Graphics::Device device{g_deviceConfig}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + device.StartRenderingCurrentFrame(); + + std::optional update; + { + auto texture = std::make_unique(context); + InitializeTexture(*texture); + update.emplace(texture->CreateDeferredUpdate()); + } + + EXPECT_FALSE(update->TryCreate2D(8, 8, 0)); + + device.FinishRenderingCurrentFrame(); +} + +TEST(Texture, NewResourceGenerationInvalidatesOlderDeferredUpdate) +{ + Babylon::Graphics::Device device{g_deviceConfig}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + device.StartRenderingCurrentFrame(); + + Babylon::Graphics::Texture texture{context}; + InitializeTexture(texture); + auto staleUpdate = texture.CreateDeferredUpdate(); + auto currentUpdate = staleUpdate; + + ASSERT_TRUE(currentUpdate.TryCreate2D(8, 8, 0)); + EXPECT_EQ(texture.Width(), 8); + EXPECT_EQ(texture.Height(), 8); + EXPECT_FALSE(staleUpdate.TryCreate2D(16, 16, 0)); + EXPECT_EQ(texture.Width(), 8); + EXPECT_EQ(texture.Height(), 8); + + texture.Dispose(); + device.FinishRenderingCurrentFrame(); +} 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..259d3cdd69 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h @@ -1,15 +1,47 @@ #pragma once #include +#include +#include namespace Babylon::Graphics { class DeviceContext; - // This class is not thread-safe. Callers must serialize all access. class Texture final { + struct DeferredUpdateState; + public: + class DeferredUpdate final + { + public: + // Matches only the texture state and generation captured by each token. + bool Matches(const DeferredUpdate& other) const; + + // Returns false if the texture was disposed, destroyed, or recreated since + // this update was captured. The token does not keep the Texture wrapper alive. + bool TryCreate2D(uint16_t width, uint16_t height, uintptr_t nativeTextureHandle); + + private: + friend class Texture; + + DeferredUpdate( + std::shared_ptr state, + uint64_t generation, + bool hasMips, + uint16_t numLayers, + bgfx::TextureFormat::Enum format, + uint64_t flags); + + std::shared_ptr m_state; + uint64_t m_generation; + bool m_hasMips; + uint16_t m_numLayers; + bgfx::TextureFormat::Enum m_format; + uint64_t m_flags; + }; + Texture(DeviceContext& deviceContext); ~Texture(); @@ -19,6 +51,7 @@ namespace Babylon::Graphics void Dispose(); bool IsValid() const; + DeferredUpdate CreateDeferredUpdate() 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); 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); @@ -56,16 +89,16 @@ namespace Babylon::Graphics // means "unset"; a generation mismatch means a mid-frame view flush has since reset the // view counter and the reservation no longer orders before later views. In both cases // consumers fall back to a freshly peeked view. - bgfx::ViewId BlitViewId() const { return m_blitViewId; } - uint32_t BlitViewIdGeneration() const { return m_blitViewIdGeneration; } - void BlitViewId(bgfx::ViewId viewId, uint32_t generation) - { - m_blitViewId = viewId; - m_blitViewIdGeneration = generation; - } + bgfx::ViewId BlitViewId() const; + uint32_t BlitViewIdGeneration() const; + void BlitViewId(bgfx::ViewId viewId, uint32_t generation); private: - void SetMetadata( + static void DisposeHandle(DeferredUpdateState& state); + static void Create2DLocked(DeferredUpdateState& state, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags, uintptr_t nativeTextureHandle); + + static void SetMetadata( + DeferredUpdateState& state, uint16_t width, uint16_t height, uint16_t depth, @@ -76,23 +109,6 @@ namespace Babylon::Graphics bgfx::TextureFormat::Enum format, uint64_t flags); - bgfx::TextureHandle m_handle{bgfx::kInvalidHandle}; - bool m_ownsHandle{false}; - uint16_t m_width{0}; - uint16_t m_height{0}; - bool m_hasMips{false}; - bool m_isCube{false}; - bool m_is3D{false}; - uint16_t m_numLayers{0}; - uint16_t m_depth{0}; - bgfx::TextureFormat::Enum m_format{bgfx::TextureFormat::Enum::Unknown}; - uint64_t m_flags{BGFX_TEXTURE_NONE}; - uint32_t m_samplerFlags{BGFX_SAMPLER_NONE}; - uint16_t m_viewFirstLayer{0}; - uint16_t m_viewNumLayers{0}; - bgfx::ViewId m_blitViewId{UINT16_MAX}; - uint32_t m_blitViewIdGeneration{0}; - uintptr_t m_deviceID; - DeviceContext& m_deviceContext; + std::shared_ptr m_deferredUpdateState; }; } diff --git a/Core/Graphics/Source/FrameBuffer.cpp b/Core/Graphics/Source/FrameBuffer.cpp index d5e3da16bd..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_defaultBackBuffer ? m_deviceContext.GetBackBufferHandle() : m_handle; + return m_useDeviceBackBuffer ? m_deviceContext.GetBackBufferHandle() : m_handle; } uint16_t FrameBuffer::Width() const diff --git a/Core/Graphics/Source/Texture.cpp b/Core/Graphics/Source/Texture.cpp index 69a0321c09..df8e4d792d 100644 --- a/Core/Graphics/Source/Texture.cpp +++ b/Core/Graphics/Source/Texture.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include namespace { @@ -17,9 +19,74 @@ namespace namespace Babylon::Graphics { + struct Texture::DeferredUpdateState final + { + explicit DeferredUpdateState(DeviceContext& deviceContext) + : DeviceID{deviceContext.GetDeviceId()} + , DeviceContextRef{deviceContext} + { + } + + std::mutex Mutex; + bgfx::TextureHandle Handle{bgfx::kInvalidHandle}; + bool OwnsHandle{false}; + uint16_t Width{0}; + uint16_t Height{0}; + bool HasMips{false}; + bool IsCube{false}; + bool Is3D{false}; + uint16_t NumLayers{0}; + uint16_t Depth{0}; + bgfx::TextureFormat::Enum Format{bgfx::TextureFormat::Enum::Unknown}; + uint64_t Flags{BGFX_TEXTURE_NONE}; + uint32_t SamplerFlags{BGFX_SAMPLER_NONE}; + uint16_t ViewFirstLayer{0}; + uint16_t ViewNumLayers{0}; + bgfx::ViewId BlitViewId{UINT16_MAX}; + uint32_t BlitViewIdGeneration{0}; + uintptr_t DeviceID; + DeviceContext& DeviceContextRef; + uint64_t Generation{}; + bool UpdatesAllowed{false}; + }; + + Texture::DeferredUpdate::DeferredUpdate( + std::shared_ptr state, + uint64_t generation, + bool hasMips, + uint16_t numLayers, + bgfx::TextureFormat::Enum format, + uint64_t flags) + : m_state{std::move(state)} + , m_generation{generation} + , m_hasMips{hasMips} + , m_numLayers{numLayers} + , m_format{format} + , m_flags{flags} + { + } + + bool Texture::DeferredUpdate::Matches(const DeferredUpdate& other) const + { + return m_state == other.m_state && m_generation == other.m_generation; + } + + bool Texture::DeferredUpdate::TryCreate2D(uint16_t width, uint16_t height, uintptr_t nativeTextureHandle) + { + std::scoped_lock lock{m_state->Mutex}; + if (!m_state->UpdatesAllowed || + m_state->Generation != m_generation) + { + return false; + } + + Texture::Create2DLocked(*m_state, width, height, m_hasMips, m_numLayers, m_format, m_flags, nativeTextureHandle); + m_generation = m_state->Generation; + return true; + } + Texture::Texture(DeviceContext& deviceContext) - : m_deviceID{deviceContext.GetDeviceId()} - , m_deviceContext{deviceContext} + : m_deferredUpdateState{std::make_shared(deviceContext)} { } @@ -30,20 +97,42 @@ namespace Babylon::Graphics void Texture::Dispose() { - if (m_ownsHandle && bgfx::isValid(m_handle) && m_deviceID == m_deviceContext.GetDeviceId()) + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + DisposeHandle(*m_deferredUpdateState); + m_deferredUpdateState->UpdatesAllowed = false; + ++m_deferredUpdateState->Generation; + } + + void Texture::DisposeHandle(DeferredUpdateState& state) + { + if (state.OwnsHandle && bgfx::isValid(state.Handle) && state.DeviceID == state.DeviceContextRef.GetDeviceId()) { - bgfx::destroy(m_handle); - m_handle = BGFX_INVALID_HANDLE; - m_ownsHandle = false; + bgfx::destroy(state.Handle); + state.Handle = BGFX_INVALID_HANDLE; + state.OwnsHandle = false; } } bool Texture::IsValid() const { - return bgfx::isValid(m_handle); + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return bgfx::isValid(m_deferredUpdateState->Handle); + } + + Texture::DeferredUpdate Texture::CreateDeferredUpdate() const + { + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return DeferredUpdate{ + m_deferredUpdateState, + m_deferredUpdateState->Generation, + m_deferredUpdateState->HasMips, + m_deferredUpdateState->NumLayers, + m_deferredUpdateState->Format, + m_deferredUpdateState->Flags}; } void Texture::SetMetadata( + DeferredUpdateState& state, uint16_t width, uint16_t height, uint16_t depth, @@ -54,20 +143,28 @@ namespace Babylon::Graphics bgfx::TextureFormat::Enum format, uint64_t flags) { - m_width = width; - m_height = height; - m_depth = depth; - m_hasMips = hasMips; - m_isCube = isCube; - m_is3D = is3D; - m_numLayers = numLayers; - m_format = format; - m_flags = flags; + state.Width = width; + state.Height = height; + state.Depth = depth; + state.HasMips = hasMips; + state.IsCube = isCube; + state.Is3D = is3D; + state.NumLayers = numLayers; + state.Format = format; + state.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) { - Dispose(); + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + Create2DLocked(*m_deferredUpdateState, width, height, hasMips, numLayers, format, flags, nativeTextureHandle); + } + + void Texture::Create2DLocked(DeferredUpdateState& state, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags, uintptr_t nativeTextureHandle) + { + DisposeHandle(state); + ++state.Generation; + state.UpdatesAllowed = false; // Create Babylon-owned textures with BGFX_TEXTURE_BLIT_DST to match web behavior. const auto createFlags = nativeTextureHandle == 0 ? flags | BGFX_TEXTURE_BLIT_DST : flags; @@ -75,147 +172,198 @@ namespace Babylon::Graphics // Make sure render targets are filled with 0 : https://registry.khronos.org/webgl/specs/latest/1.0/#TEXIMAGE2D const auto* mem = nativeTextureHandle == 0 && (flags & BGFX_TEXTURE_RT) ? GetZeroImageMemory(width, height, hasMips, numLayers, format) : nullptr; - m_handle = bgfx::createTexture2D(width, height, hasMips, numLayers, format, createFlags, mem, nativeTextureHandle); - if (!bgfx::isValid(m_handle)) + state.Handle = bgfx::createTexture2D(width, height, hasMips, numLayers, format, createFlags, mem, nativeTextureHandle); + if (!bgfx::isValid(state.Handle)) { throw std::runtime_error{"Failed to create texture"}; } - m_ownsHandle = true; - SetMetadata(width, height, 0, hasMips, false, false, numLayers, format, flags); + state.OwnsHandle = true; + SetMetadata(state, width, height, 0, hasMips, false, false, numLayers, format, flags); + state.UpdatesAllowed = true; } void Texture::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) { - bgfx::updateTexture2D(m_handle, layer, mip, x, y, width, height, mem, pitch); + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + bgfx::updateTexture2D(m_deferredUpdateState->Handle, layer, mip, x, y, width, height, mem, pitch); } void Texture::Create3D(uint16_t width, uint16_t height, uint16_t depth, bool hasMips, bgfx::TextureFormat::Enum format, uint64_t flags) { - Dispose(); + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + DisposeHandle(*m_deferredUpdateState); + ++m_deferredUpdateState->Generation; + m_deferredUpdateState->UpdatesAllowed = false; - m_handle = bgfx::createTexture3D(width, height, depth, hasMips, format, flags); - if (!bgfx::isValid(m_handle)) + m_deferredUpdateState->Handle = bgfx::createTexture3D(width, height, depth, hasMips, format, flags); + if (!bgfx::isValid(m_deferredUpdateState->Handle)) { throw std::runtime_error{"Failed to create 3D texture"}; } - m_ownsHandle = true; - SetMetadata(width, height, depth, hasMips, false, true, 1, format, flags); + m_deferredUpdateState->OwnsHandle = true; + SetMetadata(*m_deferredUpdateState, width, height, depth, hasMips, false, true, 1, format, flags); + m_deferredUpdateState->UpdatesAllowed = true; } void Texture::Update3D(uint8_t mip, uint16_t x, uint16_t y, uint16_t z, uint16_t width, uint16_t height, uint16_t depth, const bgfx::Memory* mem) { - bgfx::updateTexture3D(m_handle, mip, x, y, z, width, height, depth, mem); + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + bgfx::updateTexture3D(m_deferredUpdateState->Handle, mip, x, y, z, width, height, depth, mem); } void Texture::CreateCube(uint16_t size, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags) { - Dispose(); + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + DisposeHandle(*m_deferredUpdateState); + ++m_deferredUpdateState->Generation; + m_deferredUpdateState->UpdatesAllowed = false; - m_handle = bgfx::createTextureCube(size, hasMips, numLayers, format, flags); - if (!bgfx::isValid(m_handle)) + m_deferredUpdateState->Handle = bgfx::createTextureCube(size, hasMips, numLayers, format, flags); + if (!bgfx::isValid(m_deferredUpdateState->Handle)) { throw std::runtime_error{"Failed to create cube texture"}; } - m_ownsHandle = true; - SetMetadata(size, size, 0, hasMips, true, false, numLayers, format, flags); + m_deferredUpdateState->OwnsHandle = true; + SetMetadata(*m_deferredUpdateState, size, size, 0, hasMips, true, false, numLayers, format, flags); + m_deferredUpdateState->UpdatesAllowed = true; } void Texture::UpdateCube(uint16_t layer, uint8_t side, uint8_t mip, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const bgfx::Memory* mem, uint16_t pitch) { - bgfx::updateTextureCube(m_handle, layer, side, mip, x, y, width, height, mem, pitch); + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + bgfx::updateTextureCube(m_deferredUpdateState->Handle, layer, side, mip, x, y, width, height, mem, pitch); } void Texture::Attach(bgfx::TextureHandle handle, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags) { - Dispose(); + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + DisposeHandle(*m_deferredUpdateState); + ++m_deferredUpdateState->Generation; + m_deferredUpdateState->UpdatesAllowed = false; assert(bgfx::isValid(handle)); - m_handle = handle; + m_deferredUpdateState->Handle = handle; - m_ownsHandle = false; - SetMetadata(width, height, 0, hasMips, false, false, numLayers, format, flags); + m_deferredUpdateState->OwnsHandle = false; + SetMetadata(*m_deferredUpdateState, width, height, 0, hasMips, false, false, numLayers, format, flags); + m_deferredUpdateState->UpdatesAllowed = true; } bgfx::TextureHandle Texture::Handle() const { - return m_handle; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->Handle; } uint16_t Texture::Width() const { - return m_width; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->Width; } uint16_t Texture::Height() const { - return m_height; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->Height; } bool Texture::HasMips() const { - return m_hasMips; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->HasMips; } bool Texture::IsCube() const { - return m_isCube; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->IsCube; } bool Texture::Is3D() const { - return m_is3D; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->Is3D; } uint16_t Texture::NumLayers() const { - return m_numLayers; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->NumLayers; } uint16_t Texture::Depth() const { - return m_depth; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->Depth; } bgfx::TextureFormat::Enum Texture::Format() const { - return m_format; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->Format; } uint64_t Texture::Flags() const { - return m_flags; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->Flags; } uint32_t Texture::SamplerFlags() const { - return m_samplerFlags; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->SamplerFlags; } void Texture::SamplerFlags(uint32_t value) { - m_samplerFlags = value; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + m_deferredUpdateState->SamplerFlags = value; } uint16_t Texture::ViewFirstLayer() const { - return m_viewFirstLayer; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->ViewFirstLayer; } void Texture::ViewFirstLayer(uint16_t value) { - m_viewFirstLayer = value; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + m_deferredUpdateState->ViewFirstLayer = value; } uint16_t Texture::ViewNumLayers() const { - return m_viewNumLayers; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->ViewNumLayers; } void Texture::ViewNumLayers(uint16_t value) { - m_viewNumLayers = value; + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + m_deferredUpdateState->ViewNumLayers = value; + } + + bgfx::ViewId Texture::BlitViewId() const + { + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->BlitViewId; + } + + uint32_t Texture::BlitViewIdGeneration() const + { + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + return m_deferredUpdateState->BlitViewIdGeneration; + } + + void Texture::BlitViewId(bgfx::ViewId viewId, uint32_t generation) + { + std::scoped_lock lock{m_deferredUpdateState->Mutex}; + m_deferredUpdateState->BlitViewId = viewId; + m_deferredUpdateState->BlitViewIdGeneration = generation; } } diff --git a/Documentation/Components.md b/Documentation/Components.md index 49d22c0c90..526a57900a 100644 --- a/Documentation/Components.md +++ b/Documentation/Components.md @@ -46,6 +46,10 @@ 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/Plugins/NativeCamera/Source/Apple/CameraDevice.mm b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm index 02e419c586..36a6fd0b6a 100644 --- a/Plugins/NativeCamera/Source/Apple/CameraDevice.mm +++ b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -238,8 +239,7 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], id currentCommandBuffer{}; bool isInitialized{false}; bool refreshBgfxTexture{true}; - Graphics::Texture* bgfxTexture{}; - bgfx::TextureHandle bgfxTextureHandle{bgfx::kInvalidHandle}; + std::optional bgfxTextureUpdate{}; arcana::background_dispatcher<32> cameraSessionDispatcher{}; std::shared_ptr cancellationSource{std::make_shared()}; @@ -582,8 +582,9 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(Graphics::Texture& texture) { // Hook into AfterRender to copy over the texture without overlapping bgfx's frame encoding. - // 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, texture{&texture}, cancellationSource{m_impl->cancellationSource}] { + // The deferred update token owns only shared texture state, so collection or explicit disposal + // invalidates the update without leaving a raw Texture pointer in the queued callback. + arcana::make_task(m_impl->deviceContext->AfterRenderScheduler(), *m_impl->cancellationSource, [this, textureUpdate{texture.CreateDeferredUpdate()}, cancellationSource{m_impl->cancellationSource}]() mutable { id textureY{}; id textureCbCr{}; int64_t width{0}; @@ -614,8 +615,9 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], return; } - // 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) + // Check if we've been handed a different texture or if its resource generation changed. + if (!m_impl->bgfxTextureUpdate.has_value() || + !m_impl->bgfxTextureUpdate->Matches(textureUpdate)) { m_impl->refreshBgfxTexture = true; } @@ -633,17 +635,14 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], if (m_impl->refreshBgfxTexture && m_impl->textureRGBA != nil) { - texture->Create2D( + if (textureUpdate.TryCreate2D( static_cast(width), static_cast(height), - texture->HasMips(), - texture->NumLayers(), - texture->Format(), - texture->Flags(), - reinterpret_cast(m_impl->textureRGBA)); - m_impl->bgfxTexture = texture; - m_impl->bgfxTextureHandle = texture->Handle(); - m_impl->refreshBgfxTexture = false; + reinterpret_cast(m_impl->textureRGBA))) + { + m_impl->bgfxTextureUpdate = textureUpdate; + m_impl->refreshBgfxTexture = false; + } } if (textureY != nil && textureCbCr != nil && m_impl->textureRGBA != nil) From 7248189a8b5f31cee975e7023b2eedf7da615900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Thu, 17 Sep 2026 07:56:41 -0700 Subject: [PATCH 4/8] Resize X11 Vulkan surface with swap chain Vulkan selects the X11 surface's current extent, which left explicit swap chains at the host window's original size after Device::UpdateSize. Resize and synchronize the X11 window before bgfx creates or updates its swap chain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48fa7ec8-0cf6-48a0-b836-10b7ace38c1a --- Core/Graphics/Source/DeviceImpl.h | 2 +- Core/Graphics/Source/DeviceImpl_Vulkan.cpp | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Core/Graphics/Source/DeviceImpl.h b/Core/Graphics/Source/DeviceImpl.h index 7530ede181..2068f94e8f 100644 --- a/Core/Graphics/Source/DeviceImpl.h +++ b/Core/Graphics/Source/DeviceImpl.h @@ -146,7 +146,7 @@ namespace Babylon::Graphics // 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(); 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 } } From d8714f1fb35821226e35960e8e31f3b0890bfad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Thu, 17 Sep 2026 10:04:11 -0700 Subject: [PATCH 5/8] Preserve original D3D11 format mapping in shared extraction Restore the existing table names, entries, comments and formatting so the API migration reads as a move rather than an unrelated cleanup. Keep ASTC values scoped to the shared header's namespace and retain the required lookup and depth-view helpers. Restore the original plugin format expression. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48fa7ec8-0cf6-48a0-b836-10b7ace38c1a --- .../Babylon/Graphics/D3D11Format.h | 289 +++++++++--------- .../Source/ExternalTexture_D3D11.cpp | 3 +- 2 files changed, 147 insertions(+), 145 deletions(-) diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h b/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h index d523087113..1e65b1f4c2 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h @@ -1,10 +1,9 @@ #pragma once #include +#include #include -#include -#include #include // clang-format off @@ -13,149 +12,151 @@ 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 Astc4x4Unorm{static_cast(134)}; - inline constexpr DXGI_FORMAT Astc4x4UnormSrgb{static_cast(135)}; - inline constexpr DXGI_FORMAT Astc5x4Unorm{static_cast(138)}; - inline constexpr DXGI_FORMAT Astc5x4UnormSrgb{static_cast(139)}; - inline constexpr DXGI_FORMAT Astc5x5Unorm{static_cast(142)}; - inline constexpr DXGI_FORMAT Astc5x5UnormSrgb{static_cast(143)}; - inline constexpr DXGI_FORMAT Astc6x5Unorm{static_cast(146)}; - inline constexpr DXGI_FORMAT Astc6x5UnormSrgb{static_cast(147)}; - inline constexpr DXGI_FORMAT Astc6x6Unorm{static_cast(150)}; - inline constexpr DXGI_FORMAT Astc6x6UnormSrgb{static_cast(151)}; - inline constexpr DXGI_FORMAT Astc8x5Unorm{static_cast(154)}; - inline constexpr DXGI_FORMAT Astc8x5UnormSrgb{static_cast(155)}; - inline constexpr DXGI_FORMAT Astc8x6Unorm{static_cast(158)}; - inline constexpr DXGI_FORMAT Astc8x6UnormSrgb{static_cast(159)}; - inline constexpr DXGI_FORMAT Astc8x8Unorm{static_cast(162)}; - inline constexpr DXGI_FORMAT Astc8x8UnormSrgb{static_cast(163)}; - inline constexpr DXGI_FORMAT Astc10x5Unorm{static_cast(166)}; - inline constexpr DXGI_FORMAT Astc10x5UnormSrgb{static_cast(167)}; - inline constexpr DXGI_FORMAT Astc10x6Unorm{static_cast(170)}; - inline constexpr DXGI_FORMAT Astc10x6UnormSrgb{static_cast(171)}; - inline constexpr DXGI_FORMAT Astc10x8Unorm{static_cast(174)}; - inline constexpr DXGI_FORMAT Astc10x8UnormSrgb{static_cast(175)}; - inline constexpr DXGI_FORMAT Astc10x10Unorm{static_cast(178)}; - inline constexpr DXGI_FORMAT Astc10x10UnormSrgb{static_cast(179)}; - inline constexpr DXGI_FORMAT Astc12x10Unorm{static_cast(182)}; - inline constexpr DXGI_FORMAT Astc12x10UnormSrgb{static_cast(183)}; - inline constexpr DXGI_FORMAT Astc12x12Unorm{static_cast(186)}; - inline constexpr DXGI_FORMAT Astc12x12UnormSrgb{static_cast(187)}; + 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 Linear; - DXGI_FORMAT Srgb; + DXGI_FORMAT m_fmt; + DXGI_FORMAT m_fmtSrgb; }; - inline constexpr TextureFormatInfo TextureFormats[] = { - {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 - {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // EACR11S - {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // EACRG11 - {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // EACRG11S - {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 - {Astc4x4Unorm, Astc4x4UnormSrgb}, // ASTC4x4 - {Astc5x4Unorm, Astc5x4UnormSrgb}, // ASTC5x4 - {Astc5x5Unorm, Astc5x5UnormSrgb}, // ASTC5x5 - {Astc6x5Unorm, Astc6x5UnormSrgb}, // ASTC6x5 - {Astc6x6Unorm, Astc6x6UnormSrgb}, // ASTC6x6 - {Astc8x5Unorm, Astc8x5UnormSrgb}, // ASTC8x5 - {Astc8x6Unorm, Astc8x6UnormSrgb}, // ASTC8x6 - {Astc8x8Unorm, Astc8x8UnormSrgb}, // ASTC8x8 - {Astc10x5Unorm, Astc10x5UnormSrgb}, // ASTC10x5 - {Astc10x6Unorm, Astc10x6UnormSrgb}, // ASTC10x6 - {Astc10x8Unorm, Astc10x8UnormSrgb}, // ASTC10x8 - {Astc10x10Unorm, Astc10x10UnormSrgb}, // ASTC10x10 - {Astc12x10Unorm, Astc12x10UnormSrgb}, // ASTC12x10 - {Astc12x12Unorm, Astc12x12UnormSrgb}, // 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 + 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(std::size(TextureFormats) == bgfx::TextureFormat::Count); + static_assert(bgfx::TextureFormat::Count == BX_COUNTOF(s_textureFormat)); struct BgfxTextureFormat { @@ -170,15 +171,15 @@ namespace Babylon::Graphics::D3D11TextureFormats return std::nullopt; } - for (size_t index = 0; index < std::size(TextureFormats); ++index) + for (int i = 0; i < BX_COUNTOF(s_textureFormat); ++i) { - if (TextureFormats[index].Linear == format) + if (s_textureFormat[i].m_fmt == format) { - return BgfxTextureFormat{static_cast(index), false}; + return BgfxTextureFormat{static_cast(i), false}; } - if (TextureFormats[index].Srgb == format) + if (s_textureFormat[i].m_fmtSrgb == format) { - return BgfxTextureFormat{static_cast(index), true}; + return BgfxTextureFormat{static_cast(i), true}; } } diff --git a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp index dad22c0cb5..d45df787d0 100644 --- a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp +++ b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "ExternalTexture_Base.h" @@ -57,7 +58,7 @@ namespace Babylon::Plugins } } - const DXGI_FORMAT targetFormat = overrideFormat.value_or(desc.Format); + DXGI_FORMAT targetFormat = overrideFormat.has_value() ? overrideFormat.value() : desc.Format; if (const auto format = Graphics::D3D11TextureFormats::TryGetBgfxTextureFormat(targetFormat)) { info.Format = format->Format; From 6d7a68a906198e43cc280ba289eb3c9d100d0f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Thu, 17 Sep 2026 14:56:39 -0700 Subject: [PATCH 6/8] Address D3D11 back-buffer and runtime deployment review Reject window depth-only views whose mip, array or read-only semantics cannot be represented by bgfx SwapChain::depth. Cover rejection and recovery without restricting explicit color/depth framebuffers. Remove the added D3D11Format and ExternalBackBufferD3D11 files. Keep the original mapping in the existing D3D11 device backend, share its query through DeviceQueries, and let DeviceImpl own back-buffer state. Replace the shader-specific DLL copier with imported DXC runtime dependencies and standard TARGET_RUNTIME_DLLS deployment, including ShaderTool and installation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48fa7ec8-0cf6-48a0-b836-10b7ace38c1a --- Apps/ModuleLoadTest/CMakeLists.txt | 4 - Apps/Playground/CMakeLists.txt | 4 - Apps/UnitTests/CMakeLists.txt | 1 - .../Tests.Device.D3D11.ExternalBackBuffer.cpp | 104 +++ Core/Graphics/CMakeLists.txt | 7 - .../Include/Shared/Babylon/Graphics/Device.h | 2 + .../Babylon/Graphics/D3D11Format.h | 207 ------ .../Babylon/Graphics/DeviceQueries.h | 19 + Core/Graphics/Source/DeviceImpl.cpp | 25 +- Core/Graphics/Source/DeviceImpl.h | 19 +- Core/Graphics/Source/DeviceImpl_D3D11.cpp | 694 ++++++++++++++++++ .../Source/ExternalBackBufferD3D11.cpp | 544 -------------- .../Graphics/Source/ExternalBackBufferD3D11.h | 32 - Install/Install.cmake | 1 + .../Source/ExternalTexture_D3D11.cpp | 2 +- Plugins/ShaderCompiler/CMakeLists.txt | 82 +-- Plugins/ShaderTool/CMakeLists.txt | 5 +- 17 files changed, 887 insertions(+), 865 deletions(-) delete mode 100644 Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h delete mode 100644 Core/Graphics/Source/ExternalBackBufferD3D11.cpp delete mode 100644 Core/Graphics/Source/ExternalBackBufferD3D11.h diff --git a/Apps/ModuleLoadTest/CMakeLists.txt b/Apps/ModuleLoadTest/CMakeLists.txt index 6287b36419..1ce26bd8c3 100644 --- a/Apps/ModuleLoadTest/CMakeLists.txt +++ b/Apps/ModuleLoadTest/CMakeLists.txt @@ -37,10 +37,6 @@ target_link_libraries(ModuleLoadTest PRIVATE XMLHttpRequest ${ADDITIONAL_LIBRARIES}) -if(BABYLON_NATIVE_PLUGIN_SHADERCOMPILER) - deploy_shader_compiler_runtime(ModuleLoadTest) -endif() - add_test(NAME ModuleLoadTest COMMAND ModuleLoadTest CONFIGURATIONS Release RelWithDebInfo) # See https://gitlab.kitware.com/cmake/cmake/-/issues/23543 diff --git a/Apps/Playground/CMakeLists.txt b/Apps/Playground/CMakeLists.txt index dbd8a1bf32..643de62fde 100644 --- a/Apps/Playground/CMakeLists.txt +++ b/Apps/Playground/CMakeLists.txt @@ -161,10 +161,6 @@ target_link_libraries(Playground ${ADDITIONAL_LIBRARIES} ${BABYLON_NATIVE_PLAYGROUND_EXTENSION_LIBRARIES}) -if(BABYLON_NATIVE_PLUGIN_SHADERCOMPILER) - deploy_shader_compiler_runtime(Playground) -endif() - # 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 Playground POST_BUILD diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index 7093597ddf..452620a182 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -106,7 +106,6 @@ 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) - deploy_shader_compiler_runtime(UnitTests) endif() # NativeDraco and NativeMeshopt default to OFF, so link and exercise them only when the diff --git a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp index 3da7e0077c..7c3348ffa2 100644 --- a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp @@ -18,6 +18,8 @@ #include #include +extern Babylon::Graphics::Configuration g_deviceConfig; + namespace { winrt::com_ptr CreateDevice() @@ -134,6 +136,25 @@ namespace 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; @@ -340,3 +361,86 @@ TEST(Device, BackBufferMsaaCaptureUsesActualViewSampleCount) 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/Core/Graphics/CMakeLists.txt b/Core/Graphics/CMakeLists.txt index f399ef94c4..157d567de8 100644 --- a/Core/Graphics/CMakeLists.txt +++ b/Core/Graphics/CMakeLists.txt @@ -24,13 +24,6 @@ if(GRAPHICS_API STREQUAL "OpenGL") "Include/RendererType/OpenGL/Babylon/Graphics/GL/Texture.h") endif() -if(GRAPHICS_API STREQUAL "D3D11") - list(APPEND SOURCES - "InternalInclude/Babylon/Graphics/D3D11Format.h" - "Source/ExternalBackBufferD3D11.h" - "Source/ExternalBackBufferD3D11.cpp") -endif() - add_library(Graphics ${SOURCES}) warnings_as_errors(Graphics) diff --git a/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h b/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h index 00ff65bf0f..7e2aeacaab 100644 --- a/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h +++ b/Core/Graphics/Include/Shared/Babylon/Graphics/Device.h @@ -36,6 +36,7 @@ namespace Babylon::Graphics // Depth stencil back buffer to use instead of creating one internally. // @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 @@ -113,6 +114,7 @@ namespace Babylon::Graphics #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/D3D11Format.h b/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h deleted file mode 100644 index 1e65b1f4c2..0000000000 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/D3D11Format.h +++ /dev/null @@ -1,207 +0,0 @@ -#pragma once - -#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)); - - struct BgfxTextureFormat - { - bgfx::TextureFormat::Enum Format; - bool Srgb; - }; - - inline 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; - } - - inline 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 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/Source/DeviceImpl.cpp b/Core/Graphics/Source/DeviceImpl.cpp index eef52b5318..ea9bc9794f 100644 --- a/Core/Graphics/Source/DeviceImpl.cpp +++ b/Core/Graphics/Source/DeviceImpl.cpp @@ -11,10 +11,6 @@ #include #include -#ifdef GRAPHICS_BACK_BUFFER_SUPPORT -#include "ExternalBackBufferD3D11.h" -#endif - #if defined(__APPLE__) #include #endif @@ -146,9 +142,9 @@ namespace Babylon::Graphics bgfx::FrameBufferHandle DeviceImpl::GetBackBufferHandle() const { #ifdef GRAPHICS_BACK_BUFFER_SUPPORT - if (m_externalBackBuffer && bgfx::isValid(m_externalBackBuffer->GetFrameBuffer())) + if (bgfx::isValid(m_externalBackBuffer.FrameBuffer)) { - return m_externalBackBuffer->GetFrameBuffer(); + return m_externalBackBuffer.FrameBuffer; } #endif return m_windowFrameBuffer; @@ -747,7 +743,7 @@ namespace Babylon::Graphics m_windowHandle = nullptr; m_displayHandle = nullptr; #ifdef GRAPHICS_BACK_BUFFER_SUPPORT - m_externalBackBuffer.reset(); + DestroyExternalBackBuffer(); #endif } @@ -758,20 +754,19 @@ namespace Babylon::Graphics swapChain.height = std::max(1u, swapChain.height); #ifdef GRAPHICS_BACK_BUFFER_SUPPORT - if (m_externalBackBuffer || m_state.BackBufferColor || m_state.BackBufferDepthStencil) + 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) { - m_externalBackBuffer = std::make_unique( - m_state.BackBufferColor.get(), m_state.BackBufferDepthStencil.get(), swapChain); - if (bgfx::isValid(m_externalBackBuffer->GetFrameBuffer())) + CreateExternalBackBuffer(swapChain); + if (bgfx::isValid(m_externalBackBuffer.FrameBuffer)) { return; } - swapChain.depth = m_externalBackBuffer->GetDepthTexture(); + swapChain.depth = m_externalBackBuffer.DepthHandle; } } #endif @@ -828,7 +823,7 @@ namespace Babylon::Graphics throw std::runtime_error{"Cannot capture without a window or an external back buffer."}; } #ifdef GRAPHICS_BACK_BUFFER_SUPPORT - if (m_externalBackBuffer && bgfx::isValid(m_externalBackBuffer->GetFrameBuffer())) + if (bgfx::isValid(m_externalBackBuffer.FrameBuffer)) { return true; } @@ -855,9 +850,7 @@ namespace Babylon::Graphics #ifdef GRAPHICS_BACK_BUFFER_SUPPORT if (externalScreenShot) { - m_externalBackBuffer->ReadPixels([this](const auto& data) { - m_bgfxCallback.CompleteScreenShot(data); - }); + ReadExternalBackBuffer(); } #endif diff --git a/Core/Graphics/Source/DeviceImpl.h b/Core/Graphics/Source/DeviceImpl.h index 2068f94e8f..455a94ac14 100644 --- a/Core/Graphics/Source/DeviceImpl.h +++ b/Core/Graphics/Source/DeviceImpl.h @@ -26,10 +26,6 @@ namespace Babylon::Graphics { -#ifdef GRAPHICS_BACK_BUFFER_SUPPORT - class ExternalBackBufferD3D11; -#endif - class DeviceImpl { public: @@ -196,7 +192,20 @@ namespace Babylon::Graphics void* m_windowHandle{}; void* m_displayHandle{}; #ifdef GRAPHICS_BACK_BUFFER_SUPPORT - std::unique_ptr m_externalBackBuffer; + 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 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/ExternalBackBufferD3D11.cpp b/Core/Graphics/Source/ExternalBackBufferD3D11.cpp deleted file mode 100644 index c19cc9f792..0000000000 --- a/Core/Graphics/Source/ExternalBackBufferD3D11.cpp +++ /dev/null @@ -1,544 +0,0 @@ -#include "ExternalBackBufferD3D11.h" - -#include - -#include -#include - -#include -#include -#include -#include - -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 -{ - class ExternalBackBufferD3D11::Impl - { - public: - Impl(BackBufferColorT color, BackBufferDepthStencilT depth, const bgfx::SwapChain& descriptor) - { - 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) - { - Destroy(); - } - }); - - std::optional colorInfo; - if (color != nullptr) - { - m_colorView.copy_from(color); - colorInfo = GetColorInfo(color); - ValidateDevice(*colorInfo, bgfxDevice); - m_colorTexture = colorInfo->Texture; - m_colorInfo = *colorInfo; - m_colorTextureHandle = ImportTexture(*colorInfo); - } - - std::optional depthInfo; - if (depth != nullptr) - { - m_depthView.copy_from(depth); - depthInfo = GetDepthInfo(depth); - ValidateDevice(*depthInfo, bgfxDevice); - m_depthTexture = depthInfo->Texture; - m_depthTextureHandle = 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_depthTexture, - m_depthView); - m_depthTextureHandle = ImportTexture(*depthInfo); - } - - std::array attachments{}; - uint8_t attachmentCount{}; - if (colorInfo) - { - attachments[attachmentCount++] = MakeAttachment(m_colorTextureHandle, *colorInfo); - } - if (depthInfo && (colorInfo || descriptor.nwh == nullptr)) - { - attachments[attachmentCount++] = MakeAttachment(m_depthTextureHandle, *depthInfo); - } - - if (attachmentCount != 0) - { - m_frameBuffer = bgfx::createFrameBuffer(attachmentCount, attachments.data(), false); - if (!bgfx::isValid(m_frameBuffer)) - { - throw std::runtime_error{"Failed to create D3D11 external back buffer framebuffer."}; - } - } - - cleanupNeeded = false; - } - - ~Impl() - { - Destroy(); - } - - bgfx::FrameBufferHandle GetFrameBuffer() const - { - return m_frameBuffer; - } - - bgfx::TextureHandle GetDepthTexture() const - { - return m_depthTextureHandle; - } - - void ReadPixels(const std::function& callback) const - { - if (!m_colorInfo || !m_colorTexture) - { - throw std::runtime_error{"Cannot capture a D3D11 external back buffer without a color view."}; - } - - const bgfx::TextureFormat::Enum captureFormat = GetCaptureFormat(m_colorInfo->ViewFormat); - - winrt::com_ptr device; - m_colorTexture->GetDevice(device.put()); - winrt::com_ptr context; - device->GetImmediateContext(context.put()); - - D3D11_TEXTURE2D_DESC copyDesc{}; - copyDesc.Width = m_colorInfo->Width; - copyDesc.Height = m_colorInfo->Height; - copyDesc.MipLevels = 1; - copyDesc.ArraySize = 1; - copyDesc.Format = m_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( - m_colorInfo->Mip, - m_colorInfo->FirstLayer, - m_colorInfo->TextureDesc.MipLevels); - - if (m_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_colorTexture.get(), - sourceSubresource, - m_colorInfo->ViewFormat); - context->CopyResource(staging.get(), resolved.get()); - } - else - { - context->CopySubresourceRegion( - staging.get(), - 0, - 0, - 0, - 0, - m_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); }); - - callback(BgfxCallback::CaptureData{ - m_colorInfo->Width, - m_colorInfo->Height, - mapped.RowPitch, - captureFormat, - false, - mapped.pData, - mapped.RowPitch * m_colorInfo->Height}); - } - - private: - void Destroy() - { - if (bgfx::isValid(m_frameBuffer)) - { - bgfx::destroy(m_frameBuffer); - m_frameBuffer = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(m_colorTextureHandle)) - { - bgfx::destroy(m_colorTextureHandle); - m_colorTextureHandle = BGFX_INVALID_HANDLE; - } - if (bgfx::isValid(m_depthTextureHandle)) - { - bgfx::destroy(m_depthTextureHandle); - m_depthTextureHandle = BGFX_INVALID_HANDLE; - } - } - - winrt::com_ptr m_colorView; - winrt::com_ptr m_depthView; - winrt::com_ptr m_colorTexture; - winrt::com_ptr m_depthTexture; - std::optional m_colorInfo; - bgfx::FrameBufferHandle m_frameBuffer{BGFX_INVALID_HANDLE}; - bgfx::TextureHandle m_colorTextureHandle{BGFX_INVALID_HANDLE}; - bgfx::TextureHandle m_depthTextureHandle{BGFX_INVALID_HANDLE}; - }; - - ExternalBackBufferD3D11::ExternalBackBufferD3D11( - BackBufferColorT color, - BackBufferDepthStencilT depth, - const bgfx::SwapChain& descriptor) - : m_impl{std::make_unique(color, depth, descriptor)} - { - } - - ExternalBackBufferD3D11::~ExternalBackBufferD3D11() = default; - - bgfx::FrameBufferHandle ExternalBackBufferD3D11::GetFrameBuffer() const - { - return m_impl->GetFrameBuffer(); - } - - bgfx::TextureHandle ExternalBackBufferD3D11::GetDepthTexture() const - { - return m_impl->GetDepthTexture(); - } - - void ExternalBackBufferD3D11::ReadPixels( - const std::function& callback) const - { - m_impl->ReadPixels(callback); - } -} diff --git a/Core/Graphics/Source/ExternalBackBufferD3D11.h b/Core/Graphics/Source/ExternalBackBufferD3D11.h deleted file mode 100644 index 4b7f692365..0000000000 --- a/Core/Graphics/Source/ExternalBackBufferD3D11.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include -#include - -namespace Babylon::Graphics -{ - class ExternalBackBufferD3D11 final - { - public: - ExternalBackBufferD3D11(BackBufferColorT color, BackBufferDepthStencilT depth, const bgfx::SwapChain& descriptor); - ~ExternalBackBufferD3D11(); - - ExternalBackBufferD3D11(const ExternalBackBufferD3D11&) = delete; - ExternalBackBufferD3D11& operator=(const ExternalBackBufferD3D11&) = delete; - ExternalBackBufferD3D11(ExternalBackBufferD3D11&&) = delete; - ExternalBackBufferD3D11& operator=(ExternalBackBufferD3D11&&) = delete; - - bgfx::FrameBufferHandle GetFrameBuffer() const; - bgfx::TextureHandle GetDepthTexture() const; - void ReadPixels(const std::function& callback) const; - - private: - class Impl; - std::unique_ptr m_impl; - }; -} diff --git a/Install/Install.cmake b/Install/Install.cmake index 499fbf9527..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() diff --git a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp index d45df787d0..30784cc1d4 100644 --- a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp +++ b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/Plugins/ShaderCompiler/CMakeLists.txt b/Plugins/ShaderCompiler/CMakeLists.txt index f026e61e9c..b254d13e79 100644 --- a/Plugins/ShaderCompiler/CMakeLists.txt +++ b/Plugins/ShaderCompiler/CMakeLists.txt @@ -43,57 +43,53 @@ endif() if(GRAPHICS_API STREQUAL "D3D11") target_link_libraries(ShaderCompiler PRIVATE "d3dcompiler.lib") -endif() - -function(deploy_shader_compiler_runtime TARGET_NAME) - if(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) - - 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) - get_property(DXC_RUNTIME_WARNING_EMITTED GLOBAL PROPERTY BABYLON_DXC_RUNTIME_WARNING_EMITTED) - if(NOT DXC_RUNTIME_WARNING_EMITTED) - 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.") - set_property(GLOBAL PROPERTY BABYLON_DXC_RUNTIME_WARNING_EMITTED TRUE) - endif() - return() - endif() +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_RUNTIME_FILES - "${DXC_RUNTIME_DIR}/dxcompiler.dll" - "${DXC_RUNTIME_DIR}/dxil.dll") - - foreach(DXC_RUNTIME_FILE IN LISTS DXC_RUNTIME_FILES) + 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_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${DXC_RUNTIME_FILES} - "$" - COMMENT "Copying DirectX Shader Compiler runtime to ${TARGET_NAME}") + 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() -endfunction() +endif() # TODO: remove this once the #define in ShaderCompilerCommon gets split into separate compilation units target_compile_definitions(ShaderCompiler diff --git a/Plugins/ShaderTool/CMakeLists.txt b/Plugins/ShaderTool/CMakeLists.txt index a26cd655e6..39a79a318b 100644 --- a/Plugins/ShaderTool/CMakeLists.txt +++ b/Plugins/ShaderTool/CMakeLists.txt @@ -10,7 +10,10 @@ target_link_libraries(ShaderTool PRIVATE ShaderCacheInternal PRIVATE ShaderCache) -deploy_shader_compiler_runtime(ShaderTool) +# 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}) From 8bf2c2203cf6c3197d2249bcf6faea02a2141181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Thu, 17 Sep 2026 15:31:39 -0700 Subject: [PATCH 7/8] Keep camera texture imports synchronous and remove deferred Texture state Use the thread-safe bgfx resource API on the camera update's calling thread, after Metal conversion completes. Retain only the imported Metal resource until the frame is processed; never queue wrapper access. Restore Texture's pre-mutex fields and methods and remove its deferred update token API and tests. Cover native-resource retention across wrapper disposal, destruction and replacement in the existing D3D11 tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48fa7ec8-0cf6-48a0-b836-10b7ace38c1a --- Apps/UnitTests/CMakeLists.txt | 1 - .../Tests.Device.D3D11.ExternalBackBuffer.cpp | 51 ++++ .../Source/Tests.Texture.DeferredUpdate.cpp | 95 ------- .../Babylon/Graphics/Texture.h | 70 ++--- Core/Graphics/Source/Texture.cpp | 256 ++++-------------- .../NativeCamera/Source/Apple/CameraDevice.mm | 252 ++++++++--------- Plugins/NativeCamera/Source/CameraDevice.h | 1 + 7 files changed, 261 insertions(+), 465 deletions(-) delete mode 100644 Apps/UnitTests/Source/Tests.Texture.DeferredUpdate.cpp diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index 452620a182..09aa492e76 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -46,7 +46,6 @@ set(SOURCES "Source/Tests.ShaderCache.cpp" "Source/Tests.ShaderCompilation.cpp" "Source/Tests.ShaderCompilation.FragCoord.cpp" - "Source/Tests.Texture.DeferredUpdate.cpp" "Source/Tests.UniformPadding.cpp" "Source/Helpers.h" "Source/Helpers.${GRAPHICS_API}.${BABYLON_NATIVE_PLATFORM_IMPL_EXT}") diff --git a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp index 7c3348ffa2..367011ad62 100644 --- a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "Helpers.h" @@ -444,3 +445,53 @@ TEST(Device, WindowDepthBackBufferAcceptsDefaultViewAfterRejection) auto& context = GetContext(device, runtime); ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); } + +TEST(Device, NativeImportRetainsResourceWithoutDeferringWrapperAccess) +{ + for (uint32_t action : {0u, 1u, 2u}) + { + SCOPED_TRACE(action); + Babylon::Graphics::Device device{g_deviceConfig}; + Babylon::AppRuntime runtime{}; + auto& context = GetContext(device, runtime); + device.StartRenderingCurrentFrame(); + + auto native = std::shared_ptr{ + Helpers::CreateTexture(device.GetPlatformInfo().Device, 4, 4), + [](ID3D11Resource* resource) { Helpers::DestroyTexture(resource); }}; + std::weak_ptr retained = native; + auto texture = std::make_unique(context); + texture->Create2D(4, 4, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE, + reinterpret_cast(native.get())); + arcana::make_task(context.AfterRenderScheduler(), arcana::cancellation::none(), + [native] { (void)native; }); + native.reset(); + EXPECT_FALSE(retained.expired()); + + if (action == 0) + { + texture->Dispose(); + } + else if (action == 1) + { + texture.reset(); + } + else + { + texture->Create2D(8, 8, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE); + } + + 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); + } + } +} diff --git a/Apps/UnitTests/Source/Tests.Texture.DeferredUpdate.cpp b/Apps/UnitTests/Source/Tests.Texture.DeferredUpdate.cpp deleted file mode 100644 index 4083bcfea2..0000000000 --- a/Apps/UnitTests/Source/Tests.Texture.DeferredUpdate.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include -#include -#include -#include - -#include -#include -#include - -extern Babylon::Graphics::Configuration g_deviceConfig; - -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(); - } - - void InitializeTexture(Babylon::Graphics::Texture& texture) - { - texture.Create2D(4, 4, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE); - } -} - -TEST(Texture, DisposeInvalidatesDeferredUpdate) -{ - Babylon::Graphics::Device device{g_deviceConfig}; - Babylon::AppRuntime runtime{}; - auto& context = GetContext(device, runtime); - device.StartRenderingCurrentFrame(); - - Babylon::Graphics::Texture texture{context}; - InitializeTexture(texture); - auto update = texture.CreateDeferredUpdate(); - - texture.Dispose(); - auto disposedUpdate = texture.CreateDeferredUpdate(); - - EXPECT_FALSE(update.Matches(disposedUpdate)); - EXPECT_FALSE(update.TryCreate2D(8, 8, 0)); - EXPECT_FALSE(disposedUpdate.TryCreate2D(8, 8, 0)); - EXPECT_FALSE(texture.IsValid()); - - device.FinishRenderingCurrentFrame(); -} - -TEST(Texture, DestructionInvalidatesDeferredUpdate) -{ - Babylon::Graphics::Device device{g_deviceConfig}; - Babylon::AppRuntime runtime{}; - auto& context = GetContext(device, runtime); - device.StartRenderingCurrentFrame(); - - std::optional update; - { - auto texture = std::make_unique(context); - InitializeTexture(*texture); - update.emplace(texture->CreateDeferredUpdate()); - } - - EXPECT_FALSE(update->TryCreate2D(8, 8, 0)); - - device.FinishRenderingCurrentFrame(); -} - -TEST(Texture, NewResourceGenerationInvalidatesOlderDeferredUpdate) -{ - Babylon::Graphics::Device device{g_deviceConfig}; - Babylon::AppRuntime runtime{}; - auto& context = GetContext(device, runtime); - device.StartRenderingCurrentFrame(); - - Babylon::Graphics::Texture texture{context}; - InitializeTexture(texture); - auto staleUpdate = texture.CreateDeferredUpdate(); - auto currentUpdate = staleUpdate; - - ASSERT_TRUE(currentUpdate.TryCreate2D(8, 8, 0)); - EXPECT_EQ(texture.Width(), 8); - EXPECT_EQ(texture.Height(), 8); - EXPECT_FALSE(staleUpdate.TryCreate2D(16, 16, 0)); - EXPECT_EQ(texture.Width(), 8); - EXPECT_EQ(texture.Height(), 8); - - texture.Dispose(); - device.FinishRenderingCurrentFrame(); -} diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h index 259d3cdd69..1bd48809ed 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h @@ -1,47 +1,15 @@ #pragma once #include -#include -#include namespace Babylon::Graphics { class DeviceContext; + // This class is not thread-safe. Callers must serialize all access. class Texture final { - struct DeferredUpdateState; - public: - class DeferredUpdate final - { - public: - // Matches only the texture state and generation captured by each token. - bool Matches(const DeferredUpdate& other) const; - - // Returns false if the texture was disposed, destroyed, or recreated since - // this update was captured. The token does not keep the Texture wrapper alive. - bool TryCreate2D(uint16_t width, uint16_t height, uintptr_t nativeTextureHandle); - - private: - friend class Texture; - - DeferredUpdate( - std::shared_ptr state, - uint64_t generation, - bool hasMips, - uint16_t numLayers, - bgfx::TextureFormat::Enum format, - uint64_t flags); - - std::shared_ptr m_state; - uint64_t m_generation; - bool m_hasMips; - uint16_t m_numLayers; - bgfx::TextureFormat::Enum m_format; - uint64_t m_flags; - }; - Texture(DeviceContext& deviceContext); ~Texture(); @@ -51,7 +19,6 @@ namespace Babylon::Graphics void Dispose(); bool IsValid() const; - DeferredUpdate CreateDeferredUpdate() 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); 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); @@ -89,16 +56,16 @@ namespace Babylon::Graphics // means "unset"; a generation mismatch means a mid-frame view flush has since reset the // view counter and the reservation no longer orders before later views. In both cases // consumers fall back to a freshly peeked view. - bgfx::ViewId BlitViewId() const; - uint32_t BlitViewIdGeneration() const; - void BlitViewId(bgfx::ViewId viewId, uint32_t generation); + bgfx::ViewId BlitViewId() const { return m_blitViewId; } + uint32_t BlitViewIdGeneration() const { return m_blitViewIdGeneration; } + void BlitViewId(bgfx::ViewId viewId, uint32_t generation) + { + m_blitViewId = viewId; + m_blitViewIdGeneration = generation; + } private: - static void DisposeHandle(DeferredUpdateState& state); - static void Create2DLocked(DeferredUpdateState& state, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags, uintptr_t nativeTextureHandle); - - static void SetMetadata( - DeferredUpdateState& state, + void SetMetadata( uint16_t width, uint16_t height, uint16_t depth, @@ -109,6 +76,23 @@ namespace Babylon::Graphics bgfx::TextureFormat::Enum format, uint64_t flags); - std::shared_ptr m_deferredUpdateState; + bgfx::TextureHandle m_handle{bgfx::kInvalidHandle}; + bool m_ownsHandle{false}; + uint16_t m_width{0}; + uint16_t m_height{0}; + bool m_hasMips{false}; + bool m_isCube{false}; + bool m_is3D{false}; + uint16_t m_numLayers{0}; + uint16_t m_depth{0}; + bgfx::TextureFormat::Enum m_format{bgfx::TextureFormat::Enum::Unknown}; + uint64_t m_flags{BGFX_TEXTURE_NONE}; + uint32_t m_samplerFlags{BGFX_SAMPLER_NONE}; + uint16_t m_viewFirstLayer{0}; + uint16_t m_viewNumLayers{0}; + bgfx::ViewId m_blitViewId{UINT16_MAX}; + uint32_t m_blitViewIdGeneration{0}; + uintptr_t m_deviceID; + DeviceContext& m_deviceContext; }; } diff --git a/Core/Graphics/Source/Texture.cpp b/Core/Graphics/Source/Texture.cpp index df8e4d792d..69a0321c09 100644 --- a/Core/Graphics/Source/Texture.cpp +++ b/Core/Graphics/Source/Texture.cpp @@ -2,8 +2,6 @@ #include #include #include -#include -#include namespace { @@ -19,74 +17,9 @@ namespace namespace Babylon::Graphics { - struct Texture::DeferredUpdateState final - { - explicit DeferredUpdateState(DeviceContext& deviceContext) - : DeviceID{deviceContext.GetDeviceId()} - , DeviceContextRef{deviceContext} - { - } - - std::mutex Mutex; - bgfx::TextureHandle Handle{bgfx::kInvalidHandle}; - bool OwnsHandle{false}; - uint16_t Width{0}; - uint16_t Height{0}; - bool HasMips{false}; - bool IsCube{false}; - bool Is3D{false}; - uint16_t NumLayers{0}; - uint16_t Depth{0}; - bgfx::TextureFormat::Enum Format{bgfx::TextureFormat::Enum::Unknown}; - uint64_t Flags{BGFX_TEXTURE_NONE}; - uint32_t SamplerFlags{BGFX_SAMPLER_NONE}; - uint16_t ViewFirstLayer{0}; - uint16_t ViewNumLayers{0}; - bgfx::ViewId BlitViewId{UINT16_MAX}; - uint32_t BlitViewIdGeneration{0}; - uintptr_t DeviceID; - DeviceContext& DeviceContextRef; - uint64_t Generation{}; - bool UpdatesAllowed{false}; - }; - - Texture::DeferredUpdate::DeferredUpdate( - std::shared_ptr state, - uint64_t generation, - bool hasMips, - uint16_t numLayers, - bgfx::TextureFormat::Enum format, - uint64_t flags) - : m_state{std::move(state)} - , m_generation{generation} - , m_hasMips{hasMips} - , m_numLayers{numLayers} - , m_format{format} - , m_flags{flags} - { - } - - bool Texture::DeferredUpdate::Matches(const DeferredUpdate& other) const - { - return m_state == other.m_state && m_generation == other.m_generation; - } - - bool Texture::DeferredUpdate::TryCreate2D(uint16_t width, uint16_t height, uintptr_t nativeTextureHandle) - { - std::scoped_lock lock{m_state->Mutex}; - if (!m_state->UpdatesAllowed || - m_state->Generation != m_generation) - { - return false; - } - - Texture::Create2DLocked(*m_state, width, height, m_hasMips, m_numLayers, m_format, m_flags, nativeTextureHandle); - m_generation = m_state->Generation; - return true; - } - Texture::Texture(DeviceContext& deviceContext) - : m_deferredUpdateState{std::make_shared(deviceContext)} + : m_deviceID{deviceContext.GetDeviceId()} + , m_deviceContext{deviceContext} { } @@ -97,42 +30,20 @@ namespace Babylon::Graphics void Texture::Dispose() { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - DisposeHandle(*m_deferredUpdateState); - m_deferredUpdateState->UpdatesAllowed = false; - ++m_deferredUpdateState->Generation; - } - - void Texture::DisposeHandle(DeferredUpdateState& state) - { - if (state.OwnsHandle && bgfx::isValid(state.Handle) && state.DeviceID == state.DeviceContextRef.GetDeviceId()) + if (m_ownsHandle && bgfx::isValid(m_handle) && m_deviceID == m_deviceContext.GetDeviceId()) { - bgfx::destroy(state.Handle); - state.Handle = BGFX_INVALID_HANDLE; - state.OwnsHandle = false; + bgfx::destroy(m_handle); + m_handle = BGFX_INVALID_HANDLE; + m_ownsHandle = false; } } bool Texture::IsValid() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return bgfx::isValid(m_deferredUpdateState->Handle); - } - - Texture::DeferredUpdate Texture::CreateDeferredUpdate() const - { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return DeferredUpdate{ - m_deferredUpdateState, - m_deferredUpdateState->Generation, - m_deferredUpdateState->HasMips, - m_deferredUpdateState->NumLayers, - m_deferredUpdateState->Format, - m_deferredUpdateState->Flags}; + return bgfx::isValid(m_handle); } void Texture::SetMetadata( - DeferredUpdateState& state, uint16_t width, uint16_t height, uint16_t depth, @@ -143,28 +54,20 @@ namespace Babylon::Graphics bgfx::TextureFormat::Enum format, uint64_t flags) { - state.Width = width; - state.Height = height; - state.Depth = depth; - state.HasMips = hasMips; - state.IsCube = isCube; - state.Is3D = is3D; - state.NumLayers = numLayers; - state.Format = format; - state.Flags = flags; + m_width = width; + m_height = height; + m_depth = depth; + m_hasMips = hasMips; + m_isCube = isCube; + m_is3D = is3D; + m_numLayers = numLayers; + m_format = format; + 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) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - Create2DLocked(*m_deferredUpdateState, width, height, hasMips, numLayers, format, flags, nativeTextureHandle); - } - - void Texture::Create2DLocked(DeferredUpdateState& state, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags, uintptr_t nativeTextureHandle) - { - DisposeHandle(state); - ++state.Generation; - state.UpdatesAllowed = false; + Dispose(); // Create Babylon-owned textures with BGFX_TEXTURE_BLIT_DST to match web behavior. const auto createFlags = nativeTextureHandle == 0 ? flags | BGFX_TEXTURE_BLIT_DST : flags; @@ -172,198 +75,147 @@ namespace Babylon::Graphics // Make sure render targets are filled with 0 : https://registry.khronos.org/webgl/specs/latest/1.0/#TEXIMAGE2D const auto* mem = nativeTextureHandle == 0 && (flags & BGFX_TEXTURE_RT) ? GetZeroImageMemory(width, height, hasMips, numLayers, format) : nullptr; - state.Handle = bgfx::createTexture2D(width, height, hasMips, numLayers, format, createFlags, mem, nativeTextureHandle); - if (!bgfx::isValid(state.Handle)) + m_handle = bgfx::createTexture2D(width, height, hasMips, numLayers, format, createFlags, mem, nativeTextureHandle); + if (!bgfx::isValid(m_handle)) { throw std::runtime_error{"Failed to create texture"}; } - state.OwnsHandle = true; - SetMetadata(state, width, height, 0, hasMips, false, false, numLayers, format, flags); - state.UpdatesAllowed = true; + m_ownsHandle = true; + SetMetadata(width, height, 0, hasMips, false, false, numLayers, format, flags); } void Texture::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) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - bgfx::updateTexture2D(m_deferredUpdateState->Handle, layer, mip, x, y, width, height, mem, pitch); + bgfx::updateTexture2D(m_handle, layer, mip, x, y, width, height, mem, pitch); } void Texture::Create3D(uint16_t width, uint16_t height, uint16_t depth, bool hasMips, bgfx::TextureFormat::Enum format, uint64_t flags) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - DisposeHandle(*m_deferredUpdateState); - ++m_deferredUpdateState->Generation; - m_deferredUpdateState->UpdatesAllowed = false; + Dispose(); - m_deferredUpdateState->Handle = bgfx::createTexture3D(width, height, depth, hasMips, format, flags); - if (!bgfx::isValid(m_deferredUpdateState->Handle)) + m_handle = bgfx::createTexture3D(width, height, depth, hasMips, format, flags); + if (!bgfx::isValid(m_handle)) { throw std::runtime_error{"Failed to create 3D texture"}; } - m_deferredUpdateState->OwnsHandle = true; - SetMetadata(*m_deferredUpdateState, width, height, depth, hasMips, false, true, 1, format, flags); - m_deferredUpdateState->UpdatesAllowed = true; + m_ownsHandle = true; + SetMetadata(width, height, depth, hasMips, false, true, 1, format, flags); } void Texture::Update3D(uint8_t mip, uint16_t x, uint16_t y, uint16_t z, uint16_t width, uint16_t height, uint16_t depth, const bgfx::Memory* mem) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - bgfx::updateTexture3D(m_deferredUpdateState->Handle, mip, x, y, z, width, height, depth, mem); + bgfx::updateTexture3D(m_handle, mip, x, y, z, width, height, depth, mem); } void Texture::CreateCube(uint16_t size, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - DisposeHandle(*m_deferredUpdateState); - ++m_deferredUpdateState->Generation; - m_deferredUpdateState->UpdatesAllowed = false; + Dispose(); - m_deferredUpdateState->Handle = bgfx::createTextureCube(size, hasMips, numLayers, format, flags); - if (!bgfx::isValid(m_deferredUpdateState->Handle)) + m_handle = bgfx::createTextureCube(size, hasMips, numLayers, format, flags); + if (!bgfx::isValid(m_handle)) { throw std::runtime_error{"Failed to create cube texture"}; } - m_deferredUpdateState->OwnsHandle = true; - SetMetadata(*m_deferredUpdateState, size, size, 0, hasMips, true, false, numLayers, format, flags); - m_deferredUpdateState->UpdatesAllowed = true; + m_ownsHandle = true; + SetMetadata(size, size, 0, hasMips, true, false, numLayers, format, flags); } void Texture::UpdateCube(uint16_t layer, uint8_t side, uint8_t mip, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const bgfx::Memory* mem, uint16_t pitch) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - bgfx::updateTextureCube(m_deferredUpdateState->Handle, layer, side, mip, x, y, width, height, mem, pitch); + bgfx::updateTextureCube(m_handle, layer, side, mip, x, y, width, height, mem, pitch); } void Texture::Attach(bgfx::TextureHandle handle, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - DisposeHandle(*m_deferredUpdateState); - ++m_deferredUpdateState->Generation; - m_deferredUpdateState->UpdatesAllowed = false; + Dispose(); assert(bgfx::isValid(handle)); - m_deferredUpdateState->Handle = handle; + m_handle = handle; - m_deferredUpdateState->OwnsHandle = false; - SetMetadata(*m_deferredUpdateState, width, height, 0, hasMips, false, false, numLayers, format, flags); - m_deferredUpdateState->UpdatesAllowed = true; + m_ownsHandle = false; + SetMetadata(width, height, 0, hasMips, false, false, numLayers, format, flags); } bgfx::TextureHandle Texture::Handle() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->Handle; + return m_handle; } uint16_t Texture::Width() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->Width; + return m_width; } uint16_t Texture::Height() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->Height; + return m_height; } bool Texture::HasMips() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->HasMips; + return m_hasMips; } bool Texture::IsCube() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->IsCube; + return m_isCube; } bool Texture::Is3D() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->Is3D; + return m_is3D; } uint16_t Texture::NumLayers() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->NumLayers; + return m_numLayers; } uint16_t Texture::Depth() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->Depth; + return m_depth; } bgfx::TextureFormat::Enum Texture::Format() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->Format; + return m_format; } uint64_t Texture::Flags() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->Flags; + return m_flags; } uint32_t Texture::SamplerFlags() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->SamplerFlags; + return m_samplerFlags; } void Texture::SamplerFlags(uint32_t value) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - m_deferredUpdateState->SamplerFlags = value; + m_samplerFlags = value; } uint16_t Texture::ViewFirstLayer() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->ViewFirstLayer; + return m_viewFirstLayer; } void Texture::ViewFirstLayer(uint16_t value) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - m_deferredUpdateState->ViewFirstLayer = value; + m_viewFirstLayer = value; } uint16_t Texture::ViewNumLayers() const { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->ViewNumLayers; + return m_viewNumLayers; } void Texture::ViewNumLayers(uint16_t value) { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - m_deferredUpdateState->ViewNumLayers = value; - } - - bgfx::ViewId Texture::BlitViewId() const - { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->BlitViewId; - } - - uint32_t Texture::BlitViewIdGeneration() const - { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - return m_deferredUpdateState->BlitViewIdGeneration; - } - - void Texture::BlitViewId(bgfx::ViewId viewId, uint32_t generation) - { - std::scoped_lock lock{m_deferredUpdateState->Mutex}; - m_deferredUpdateState->BlitViewId = viewId; - m_deferredUpdateState->BlitViewIdGeneration = generation; + m_viewNumLayers = value; } } diff --git a/Plugins/NativeCamera/Source/Apple/CameraDevice.mm b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm index 36a6fd0b6a..149f055043 100644 --- a/Plugins/NativeCamera/Source/Apple/CameraDevice.mm +++ b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm @@ -239,7 +239,8 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], id currentCommandBuffer{}; bool isInitialized{false}; bool refreshBgfxTexture{true}; - std::optional bgfxTextureUpdate{}; + Graphics::Texture* bgfxTexture{}; + bgfx::TextureHandle bgfxTextureHandle{bgfx::kInvalidHandle}; arcana::background_dispatcher<32> cameraSessionDispatcher{}; std::shared_ptr cancellationSource{std::make_shared()}; @@ -581,148 +582,151 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], CameraDevice::CameraDimensions CameraDevice::UpdateCameraTexture(Graphics::Texture& texture) { - // Hook into AfterRender to copy over the texture without overlapping bgfx's frame encoding. - // The deferred update token owns only shared texture state, so collection or explicit disposal - // invalidates the update without leaving a raw Texture pointer in the queued callback. - arcana::make_task(m_impl->deviceContext->AfterRenderScheduler(), *m_impl->cancellationSource, [this, textureUpdate{texture.CreateDeferredUpdate()}, cancellationSource{m_impl->cancellationSource}]() mutable { - 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 we've been handed a different texture or if its resource generation changed. - if (!m_impl->bgfxTextureUpdate.has_value() || - !m_impl->bgfxTextureUpdate->Matches(textureUpdate)) - { - 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 (m_impl->refreshBgfxTexture && m_impl->textureRGBA != nil) - { - if (textureUpdate.TryCreate2D( - static_cast(width), - static_cast(height), - reinterpret_cast(m_impl->textureRGBA))) - { - m_impl->bgfxTextureUpdate = textureUpdate; - m_impl->refreshBgfxTexture = false; - } - } - - 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; + // 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)); + m_impl->bgfxTexture = &texture; + m_impl->bgfxTextureHandle = texture.Handle(); + m_impl->refreshBgfxTexture = false; + + // Keep only the native resource alive until bgfx processes the import. + arcana::make_task(m_impl->deviceContext->AfterRenderScheduler(), arcana::cancellation::none(), + [textureRGBA = m_impl->textureRGBA] { (void)textureRGBA; }); + } - // 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 diff --git a/Plugins/NativeCamera/Source/CameraDevice.h b/Plugins/NativeCamera/Source/CameraDevice.h index 4d8686a628..5003f2168e 100644 --- a/Plugins/NativeCamera/Source/CameraDevice.h +++ b/Plugins/NativeCamera/Source/CameraDevice.h @@ -99,6 +99,7 @@ namespace Babylon::Plugins arcana::task OpenAsync(const CameraTrack& track); void Close(); + // Access to the supplied Texture wrapper must complete before this call returns. CameraDimensions UpdateCameraTexture(Graphics::Texture& texture); TakePhotoTask TakePhotoAsync(PhotoSettings photoSettings); From bd2a08fb962d6fd1e99fce740a34f48b4baab78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Thu, 17 Sep 2026 17:45:24 -0700 Subject: [PATCH 8/8] Retain imported camera textures through handle destruction Keep native Metal ownership with the Texture wrapper and release retired resources only after a full render boundary processes their destruction. Keep imports synchronous without adding wrapper locks or deferred access. Cover producer release, replacement, and disposal on D3D11 and Metal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48fa7ec8-0cf6-48a0-b836-10b7ace38c1a --- .../Tests.Device.D3D11.ExternalBackBuffer.cpp | 50 -------- .../Tests.ExternalTexture.Lifecycle.cpp | 110 ++++++++++++++++++ .../Babylon/Graphics/Texture.h | 5 +- Core/Graphics/Source/Texture.cpp | 13 ++- .../NativeCamera/Source/Apple/CameraDevice.mm | 13 +-- 5 files changed, 131 insertions(+), 60 deletions(-) diff --git a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp index 367011ad62..e1a0532b22 100644 --- a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp @@ -445,53 +445,3 @@ TEST(Device, WindowDepthBackBufferAcceptsDefaultViewAfterRejection) auto& context = GetContext(device, runtime); ExpectSolidColor(ClearAndCapture(device, context, 0x4080c0ff), 32, 24, 0x4080c0ff); } - -TEST(Device, NativeImportRetainsResourceWithoutDeferringWrapperAccess) -{ - for (uint32_t action : {0u, 1u, 2u}) - { - SCOPED_TRACE(action); - Babylon::Graphics::Device device{g_deviceConfig}; - Babylon::AppRuntime runtime{}; - auto& context = GetContext(device, runtime); - device.StartRenderingCurrentFrame(); - - auto native = std::shared_ptr{ - Helpers::CreateTexture(device.GetPlatformInfo().Device, 4, 4), - [](ID3D11Resource* resource) { Helpers::DestroyTexture(resource); }}; - std::weak_ptr retained = native; - auto texture = std::make_unique(context); - texture->Create2D(4, 4, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE, - reinterpret_cast(native.get())); - arcana::make_task(context.AfterRenderScheduler(), arcana::cancellation::none(), - [native] { (void)native; }); - native.reset(); - EXPECT_FALSE(retained.expired()); - - if (action == 0) - { - texture->Dispose(); - } - else if (action == 1) - { - texture.reset(); - } - else - { - texture->Create2D(8, 8, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE); - } - - 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); - } - } -} 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/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/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/Plugins/NativeCamera/Source/Apple/CameraDevice.mm b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm index 149f055043..b130e56ed5 100644 --- a/Plugins/NativeCamera/Source/Apple/CameraDevice.mm +++ b/Plugins/NativeCamera/Source/Apple/CameraDevice.mm @@ -717,14 +717,11 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]], texture.NumLayers(), texture.Format(), texture.Flags(), - reinterpret_cast(m_impl->textureRGBA)); + 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; - - // Keep only the native resource alive until bgfx processes the import. - arcana::make_task(m_impl->deviceContext->AfterRenderScheduler(), arcana::cancellation::none(), - [textureRGBA = m_impl->textureRGBA] { (void)textureRGBA; }); } // To match the web implementation if the sensor is rotated into a portrait orientation then the width and height @@ -838,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 maintains the imported texture's - // lifetime until after the render pass. Otherwise bgfx could access a released Metal texture. + // 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];