From 227eac0d75de005d5357ae0d2ecee1f0988ea6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Sat, 17 Oct 2026 00:10:37 -0700 Subject: [PATCH] Metal: Fixed occlusion queries and depth texture data, added HDR10 and unretained command buffers. Closes #2356, #2017, #1827, #2974, #3642. (#4060) --- docs/build.rst | 47 +++++ docs/examples.rst | 11 + examples/55-hdr10/hdr10.cpp | 309 +++++++++++++++++++++++++++++ examples/common/entry/entry_ios.mm | 24 +-- scripts/bgfx.lua | 3 + scripts/genie.lua | 3 +- scripts/geometryv.lua | 6 +- scripts/texturev.lua | 6 +- src/bgfx.cpp | 2 +- src/renderer_mtl.cpp | 165 +++++++++++---- src/renderer_mtl.h | 55 +++-- 11 files changed, 559 insertions(+), 72 deletions(-) create mode 100644 examples/55-hdr10/hdr10.cpp diff --git a/docs/build.rst b/docs/build.rst index e14606e17..7aa048795 100644 --- a/docs/build.rst +++ b/docs/build.rst @@ -209,6 +209,53 @@ Linux make linux-release64 +Android +~~~~~~~ + +With ``ANDROID_NDK_ROOT`` set (see `Prerequisites <#prerequisites>`_): + +:: + + make android-arm64-release + +Libraries are in ``.build/android-arm64/bin``. Link ``libbgfxRelease.a``, ``libbimgRelease.a`` and +``libbxRelease.a`` into your native library, and add ``bgfx/include``, ``bimg/include`` and +``bx/include`` to its include paths. + +iOS +~~~ + +For the command line: + +:: + + make ios-arm64-release + +Libraries are in ``.build/ios-arm64/bin``. To use them in your own Xcode project: + +1. Link ``libbgfxRelease.a``, ``libbimgRelease.a`` and ``libbxRelease.a``. +2. Link the ``Foundation``, ``CoreGraphics``, ``QuartzCore``, ``UIKit`` and ``Metal`` frameworks, + plus ``VideoToolbox``, ``CoreMedia`` and ``CoreVideo`` for video decode. +3. Add ``bgfx/include``, ``bimg/include`` and ``bx/include`` to *Header Search Paths*. +4. Pass a ``CAMetalLayer``, or an ``MTKView``, as ``bgfx::PlatformData::nwh``. + +Or build ``src/amalgamated.mm`` directly in your project instead of linking the libraries +(see `Amalgamated Build <#amalgamated-build>`_). + +For the Xcode IDE: + +:: + + ../bx/tools/bin/darwin/genie --with-combined-examples --xcode=ios xcode9 + open .build/projects/xcode9-ios/bgfx.xcworkspace + +For tvOS, generate gmake projects with ``--gcc=tvos-arm64``: + +:: + + ../bx/tools/bin/darwin/genie --gcc=tvos-arm64 gmake + make -R -C .build/projects/gmake-tvos-arm64 config=release + For more options, see `General (directly with GENie) <#general-directly-with-genie>`_. WinRT / UWP diff --git a/docs/examples.rst b/docs/examples.rst index c1071a3d4..1ae17b5d5 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -724,3 +724,14 @@ https://sluglibrary.com/ .. figure:: https://github.com/bkaradzic/bgfx/raw/master/examples/51-gpufont/screenshot.png :alt: example-51-gpufont + +`55-hdr10 `__ +------------------------------------------------------------------------------ + +HDR10 test pattern. Gray steps at 0 to 10000 nits and BT.2020 color bars at 203 nits, PQ encoded into +an RGB10A2 back buffer, or as multiples of SDR white in an RGBA16F extended linear back buffer. HDR10 +can be toggled to compare HDR output against SDR on an HDR capable display. + +Reference(s): + - `ITU-R BT.2100 `__. + - `ITU-R BT.2408 `__. diff --git a/examples/55-hdr10/hdr10.cpp b/examples/55-hdr10/hdr10.cpp new file mode 100644 index 000000000..1234bda7e --- /dev/null +++ b/examples/55-hdr10/hdr10.cpp @@ -0,0 +1,309 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#include "common.h" +#include "bgfx_utils.h" +#include "imgui/imgui.h" + +#include + +namespace +{ + +constexpr uint32_t kSwapChainFlags = 0 + | BGFX_SWAP_CHAIN_FULLSCREEN_MASK + | BGFX_SWAP_CHAIN_MSAA_MASK + | BGFX_SWAP_CHAIN_SRGB_BACKBUFFER + | BGFX_SWAP_CHAIN_HDR10 + | BGFX_SWAP_CHAIN_HIDPI + | BGFX_SWAP_CHAIN_TRANSPARENT_BACKBUFFER + ; + +constexpr uint32_t kNumGray = 7; +constexpr uint32_t kNumBars = 6; + +constexpr float kGrayNits[kNumGray] = { 0.0f, 100.0f, 203.0f, 400.0f, 1000.0f, 4000.0f, 10000.0f }; +constexpr float kGrayScale[kNumGray] = { 0.0f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f }; +constexpr float kBarNits = 203.0f; + +constexpr float kBarColor[kNumBars][3] = +{ + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + { 0.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 1.0f }, + { 1.0f, 1.0f, 0.0f }, +}; + +constexpr const char* kBarName[kNumBars] = { "R", "G", "B", "C", "M", "Y" }; + +float encodePq(float _nits) +{ + constexpr float m1 = 2610.0f/16384.0f; + constexpr float m2 = 2523.0f/4096.0f*128.0f; + constexpr float c1 = 3424.0f/4096.0f; + constexpr float c2 = 2413.0f/4096.0f*32.0f; + constexpr float c3 = 2392.0f/4096.0f*32.0f; + + const float yy = bx::pow(bx::clamp(_nits/10000.0f, 0.0f, 1.0f), m1); + + return bx::pow( (c1 + c2*yy) / (1.0f + c3*yy), m2); +} + +class ExampleHdr10 : public entry::AppI +{ +public: + ExampleHdr10(const char* _name, const char* _description, const char* _url) + : entry::AppI(_name, _description, _url) + { + } + + void init(int32_t _argc, const char* const* _argv, uint32_t _width, uint32_t _height) override + { + Args args(_argc, _argv); + + m_width = _width; + m_height = _height; + m_debug = BGFX_DEBUG_NONE; + m_reset = BGFX_RESET_VSYNC|BGFX_RESET_HDR10; + m_pq = true; + + bgfx::Init init; + init.type = args.m_type; + init.vendorId = args.m_pciId; + init.swapChain.nwh = entry::getNativeWindowHandle(entry::kDefaultWindowHandle); + init.swapChain.ndt = entry::getNativeDisplayHandle(); + init.platformData.type = entry::getNativeWindowHandleType(); + init.swapChain.width = m_width; + init.swapChain.height = m_height; + init.swapChain.formatColor = getFormat(); + init.reset = m_reset; + bgfx::init(init); + + bgfx::setDebug(m_debug); + + m_appliedWidth = m_width; + m_appliedHeight = m_height; + m_appliedReset = m_reset; + m_appliedPq = m_pq; + + imguiCreate(); + } + + int shutdown() override + { + imguiDestroy(); + + bgfx::shutdown(); + + return 0; + } + + bgfx::TextureFormat::Enum getFormat() const + { + return m_pq + ? bgfx::TextureFormat::RGB10A2 + : bgfx::TextureFormat::RGBA16F + ; + } + + void setPatch(uint8_t _index, float _red, float _green, float _blue) + { + const float rgba[4] = { _red, _green, _blue, 1.0f }; + bgfx::setPaletteColor(_index, rgba); + } + + float encode(float _nits, float _scale) const + { + return m_pq ? encodePq(_nits) : _scale; + } + + void imguiLabel(float _x, float _y, const char* _id, const char* _text) + { + ImGui::SetNextWindowPos(ImVec2(_x, _y), 0, ImVec2(0.5f, 1.0f) ); + ImGui::SetNextWindowBgAlpha(0.5f); + + ImGui::Begin(_id, NULL, 0 + | ImGuiWindowFlags_NoInputs + | ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoSavedSettings + | ImGuiWindowFlags_AlwaysAutoResize + ); + ImGui::Text("%s", _text); + ImGui::End(); + } + + void submitPatch(bgfx::ViewId _view, uint8_t _index, uint16_t _x, uint16_t _y, uint16_t _width, uint16_t _height) + { + bgfx::setViewRect(_view, _x, _y, _width, _height); + bgfx::setViewClear(_view, BGFX_CLEAR_COLOR, 1.0f, 0, _index); + bgfx::touch(_view); + } + + bool update() override + { + if (entry::processEvents(m_width, m_height, m_debug, m_reset, &m_mouseState) ) + { + return false; + } + + imguiBeginFrame(m_mouseState.m_mx + , m_mouseState.m_my + , (m_mouseState.m_buttons[entry::MouseButton::Left ] ? IMGUI_MBUT_LEFT : 0) + | (m_mouseState.m_buttons[entry::MouseButton::Right ] ? IMGUI_MBUT_RIGHT : 0) + | (m_mouseState.m_buttons[entry::MouseButton::Middle] ? IMGUI_MBUT_MIDDLE : 0) + , m_mouseState.m_mz + , uint16_t(m_width) + , uint16_t(m_height) + ); + + showExampleDialog(this); + + bool hdr10 = 0 != (m_reset & BGFX_RESET_HDR10); + + ImGui::SetNextWindowPos(ImVec2(m_width - m_width / 5.0f - 10.0f, 10.0f), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(m_width / 5.0f, m_height / 6.0f), ImGuiCond_FirstUseEver); + ImGui::Begin("Settings", NULL, 0); + + if (ImGui::RadioButton("PQ, RGB10A2", m_pq) ) + { + m_pq = true; + } + + if (ImGui::RadioButton("Linear, RGBA16F", !m_pq) ) + { + m_pq = false; + } + + ImGui::Checkbox("HDR10", &hdr10); + + ImGui::End(); + + const float labelRowHeight = float(m_height*2/5); + + char id[32]; + char text[128]; + + for (uint32_t ii = 0; ii < kNumGray; ++ii) + { + if (m_pq) + { + bx::snprintf(text, BX_COUNTOF(text), "%.0f nits", kGrayNits[ii]); + } + else + { + bx::snprintf(text, BX_COUNTOF(text), "%.1fx white", kGrayScale[ii]); + } + + bx::snprintf(id, BX_COUNTOF(id), "gray%d", ii); + imguiLabel(m_width*(ii + 0.5f)/kNumGray, labelRowHeight - 10.0f, id, text); + } + + for (uint32_t ii = 0; ii < kNumBars; ++ii) + { + if (m_pq) + { + bx::snprintf(text, BX_COUNTOF(text), "%s %.0f nits", kBarName[ii], kBarNits); + } + else + { + bx::snprintf(text, BX_COUNTOF(text), "%s 1.0x white", kBarName[ii]); + } + + bx::snprintf(id, BX_COUNTOF(id), "bar%d", ii); + imguiLabel(m_width*(ii + 0.5f)/kNumBars, 2.0f*labelRowHeight - 10.0f, id, text); + } + + const bgfx::Caps* caps = bgfx::getCaps(); + + bx::snprintf(text, BX_COUNTOF(text), "BGFX_CAPS_HDR10: %s, back buffer: %s" + , 0 != (caps->supported & BGFX_CAPS_HDR10) ? "supported" : "not supported" + , m_pq ? "RGB10A2, PQ BT.2020" : "RGBA16F, extended linear sRGB" + ); + imguiLabel(m_width*0.5f, m_height - 10.0f, "status", text); + + imguiEndFrame(); + + m_reset = (m_reset & ~BGFX_RESET_HDR10) | (hdr10 ? BGFX_RESET_HDR10 : 0); + + if (m_width != m_appliedWidth + || m_height != m_appliedHeight + || m_reset != m_appliedReset + || m_pq != m_appliedPq) + { + m_appliedWidth = m_width; + m_appliedHeight = m_height; + m_appliedReset = m_reset; + m_appliedPq = m_pq; + + bgfx::SwapChain swapChain; + swapChain.width = m_width; + swapChain.height = m_height; + swapChain.flags = m_reset & kSwapChainFlags; + swapChain.formatColor = getFormat(); + + bgfx::reset(m_reset & ~kSwapChainFlags, &swapChain); + } + + bgfx::setViewRect(0, 0, 0, uint16_t(m_width), uint16_t(m_height) ); + bgfx::setViewClear(0, BGFX_CLEAR_COLOR, 0x000000ff, 1.0f, 0); + bgfx::touch(0); + + const uint16_t rowHeight = uint16_t(m_height*2/5); + + for (uint32_t ii = 0; ii < kNumGray; ++ii) + { + const float value = encode(kGrayNits[ii], kGrayScale[ii]); + setPatch(uint8_t(ii), value, value, value); + + const uint16_t x0 = uint16_t(m_width* ii /kNumGray); + const uint16_t x1 = uint16_t(m_width*(ii + 1)/kNumGray); + submitPatch(bgfx::ViewId(1 + ii), uint8_t(ii), x0, 0, x1 - x0, rowHeight); + } + + for (uint32_t ii = 0; ii < kNumBars; ++ii) + { + const float on = encode(kBarNits, 1.0f); + setPatch(uint8_t(kNumGray + ii) + , kBarColor[ii][0]*on + , kBarColor[ii][1]*on + , kBarColor[ii][2]*on + ); + + const uint16_t x0 = uint16_t(m_width* ii /kNumBars); + const uint16_t x1 = uint16_t(m_width*(ii + 1)/kNumBars); + submitPatch(bgfx::ViewId(1 + kNumGray + ii), uint8_t(kNumGray + ii), x0, rowHeight, x1 - x0, rowHeight); + } + + bgfx::frame(); + + return true; + } + + entry::MouseState m_mouseState; + + uint32_t m_width; + uint32_t m_height; + uint32_t m_debug; + uint32_t m_reset; + bool m_pq; + + uint32_t m_appliedWidth; + uint32_t m_appliedHeight; + uint32_t m_appliedReset; + bool m_appliedPq; +}; + +} // namespace + +ENTRY_IMPLEMENT_MAIN( + ExampleHdr10 + , "55-hdr10" + , "HDR10 test pattern." + , "https://bkaradzic.github.io/bgfx/examples.html#hdr10" + ); diff --git a/examples/common/entry/entry_ios.mm b/examples/common/entry/entry_ios.mm index 8d91519bf..3b4174e1b 100644 --- a/examples/common/entry/entry_ios.mm +++ b/examples/common/entry/entry_ios.mm @@ -9,13 +9,7 @@ #import #import -#import - -#if __IPHONE_8_0 && !TARGET_IPHONE_SIMULATOR // check if sdk/target supports metal -# import -# import -# define HAS_METAL_SDK -#endif +#import #include @@ -190,21 +184,7 @@ using namespace entry; + (Class)layerClass { -#ifdef HAS_METAL_SDK - static id device = NULL; - Class metalClass = NSClassFromString(@"CAMetalLayer"); //is metal runtime sdk available - if ( metalClass != nil) - { - device = MTLCreateSystemDefaultDevice(); // is metal supported on this device (is there a better way to do this - without creating device ?) - if (NULL != device) - { - [device retain]; - return metalClass; - } - } -#endif - - return [CAEAGLLayer class]; + return [CAMetalLayer class]; } - (id)initWithFrame:(CGRect)rect diff --git a/scripts/bgfx.lua b/scripts/bgfx.lua index b938f4d16..53cc8d62f 100644 --- a/scripts/bgfx.lua +++ b/scripts/bgfx.lua @@ -139,6 +139,9 @@ function bgfxProjectBase(_kind, _defines) "-Wno-microsoft-const-init", -- default initialization of an object of const type '' without a user-provided default constructor is a Microsoft extension } + configuration { "ios* or tvos* or xros*" } + buildoptions { "-x objective-c++" } + configuration { "osx*" } buildoptions { "-x objective-c++" } -- additional build option for osx linkoptions { diff --git a/scripts/genie.lua b/scripts/genie.lua index 062be8db3..9bc0047a1 100644 --- a/scripts/genie.lua +++ b/scripts/genie.lua @@ -403,9 +403,9 @@ function exampleProjectDefaults() kind "ConsoleApp" linkoptions { "-framework CoreFoundation", + "-framework CoreGraphics", "-framework Foundation", "-framework IOKit", - "-framework OpenGLES", "-framework QuartzCore", "-framework UIKit", "-weak_framework Metal", @@ -591,6 +591,7 @@ or _OPTIONS["with-combined-examples"] then , "52-layered" , "53-sky2" , "54-s2h" + , "55-hdr10" ) diff --git a/scripts/geometryv.lua b/scripts/geometryv.lua index 4ed06c46f..e0531e8e9 100644 --- a/scripts/geometryv.lua +++ b/scripts/geometryv.lua @@ -149,11 +149,15 @@ project ("geometryv") kind "ConsoleApp" linkoptions { "-framework CoreFoundation", + "-framework CoreGraphics", "-framework Foundation", "-framework IOKit", - "-framework OpenGLES", "-framework QuartzCore", "-framework UIKit", + "-weak_framework Metal", + "-weak_framework VideoToolbox", + "-weak_framework CoreMedia", + "-weak_framework CoreVideo", } configuration { "xcode4", "ios" } diff --git a/scripts/texturev.lua b/scripts/texturev.lua index b355e0c08..3489a369a 100644 --- a/scripts/texturev.lua +++ b/scripts/texturev.lua @@ -209,11 +209,15 @@ project "texturev" kind "ConsoleApp" linkoptions { "-framework CoreFoundation", + "-framework CoreGraphics", "-framework Foundation", "-framework IOKit", - "-framework OpenGLES", "-framework QuartzCore", "-framework UIKit", + "-weak_framework Metal", + "-weak_framework VideoToolbox", + "-weak_framework CoreMedia", + "-weak_framework CoreVideo", } configuration { "xcode*", "ios" } diff --git a/src/bgfx.cpp b/src/bgfx.cpp index 3af183f76..4d48653d2 100644 --- a/src/bgfx.cpp +++ b/src/bgfx.cpp @@ -187,7 +187,7 @@ namespace bgfx , 1 ); - bimg::imageWriteTga(&writer, _width, _height, _pitch, _data, false, _yflip); + bimg::imageWriteTga(&writer, _width, _height, dstPitch, dst, false, _yflip); bx::free(g_allocator, dst); } diff --git a/src/renderer_mtl.cpp b/src/renderer_mtl.cpp index 5fee498e1..6f74c5c4e 100644 --- a/src/renderer_mtl.cpp +++ b/src/renderer_mtl.cpp @@ -674,10 +674,11 @@ BX_PRAGMA_DIAGNOSTIC_POP(); if (false || TextureFormat::BGRA8 == _fmt + || TextureFormat::RGB10A2 == _fmt || TextureFormat::RGBA16F == _fmt ) { - // CAMetalLayer accepts BGRA8Unorm(_sRGB) and RGBA16Float as drawable pixel formats. + // CAMetalLayer accepts BGRA8Unorm(_sRGB), RGB10A2Unorm and RGBA16Float as drawable pixel formats. caps |= BGFX_CAPS_FORMAT_TEXTURE_BACKBUFFER; } @@ -1011,6 +1012,19 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames | BGFX_CAPS_VERTEX_ATTRIB_UINT10 ); +#if BX_PLATFORM_OSX + if (__builtin_available(macOS 11.0, *) ) + { + void* screen = MtlObjAccess::send(objc_lookUpClass("NSScreen"), sel_registerName("mainScreen") ); + + if (NULL != screen + && 1.0 < MtlObjAccess::send(screen, sel_registerName("maximumPotentialExtendedDynamicRangeColorComponentValue") ) ) + { + g_caps.supported |= BGFX_CAPS_HDR10; + } + } +#endif // BX_PLATFORM_OSX + g_caps.supported |= (m_device->supportsFamily(MTL::GPUFamilyApple7) || m_device->supportsFamily(MTL::GPUFamilyMac2) ) ? BGFX_CAPS_PRIMITIVE_ID : 0 @@ -2048,12 +2062,12 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames if (NULL != frameBuffer.m_swapChain && frameBuffer.m_swapChain->m_drawableTexture) { - MTL_RELEASE_I(frameBuffer.m_swapChain->m_drawableTexture); + MTL_RELEASE_DEFERRED(frameBuffer.m_swapChain->m_drawableTexture); if (NULL != frameBuffer.m_swapChain->m_drawable) { presentCommandBuffer->presentDrawable( (MTL::Drawable*)frameBuffer.m_swapChain->m_drawable); - MTL_RELEASE_I(frameBuffer.m_swapChain->m_drawable); + MTL_RELEASE_DEFERRED(frameBuffer.m_swapChain->m_drawable); } } } @@ -2143,7 +2157,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames if (NULL == m_screenshotBlitRenderPipelineState || m_screenshotBlitPixelFormat != _pixelFormat) { - MTL_RELEASE_I(m_screenshotBlitRenderPipelineState); + MTL_RELEASE_DEFERRED(m_screenshotBlitRenderPipelineState); reset(m_renderPipelineDescriptor); m_renderPipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(_pixelFormat); @@ -2437,7 +2451,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames , MTL::Origin::Make(_rect.m_x, _rect.m_y, 0) ); - MTL_RELEASE_I(buffer); + MTL_RELEASE_DEFERRED(buffer); } endBlitEncoding(); @@ -3875,7 +3889,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames void ChunkedScratchBufferMtl::destroyChunk(ChunkMtl& _chunk) { - MTL_RELEASE_W(_chunk.buffer, 0); + MTL_RELEASE_DEFERRED(_chunk.buffer); } void ChunkedScratchBufferMtl::flushChunk(ChunkMtl& _chunk, uint32_t _size) @@ -4569,7 +4583,33 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames ; } - m_ptr->replaceRegion(region, lod, side, data, bytesPerRow, bytesPerImage); + if (MTL::StorageModePrivate == m_ptr->storageMode() ) + { + const uint32_t size = convert + ? width*height*depth*4 + : mip.m_size + ; + + MTL::Buffer* staging = s_renderMtl->m_device->newBuffer(data, size, MTL::ResourceCPUCacheModeDefaultCache); + + s_renderMtl->getBlitCommandEncoder()->copyFromBuffer( + staging + , 0 + , bytesPerRow + , bytesPerImage + , MTL::Size::Make(width, height, depth) + , m_ptr + , side + , lod + , MTL::Origin::Make(0, 0, 0) + ); + + s_renderMtl->m_cmd.release(staging); + } + else + { + m_ptr->replaceRegion(region, lod, side, data, bytesPerRow, bytesPerImage); + } } width >>= 1; @@ -4594,18 +4634,18 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames if (0 == (m_flags & BGFX_SAMPLER_INTERNAL_SHARED) ) { - MTL_RELEASE_W(m_ptr, 0); + MTL_RELEASE_DEFERRED(m_ptr); } - MTL_RELEASE_W(m_ptrMsaa, 0); - MTL_RELEASE_W(m_ptrStencil, 0); - MTL_RELEASE_W(m_ptrAlt, 0); - MTL_RELEASE_W(m_ptrMsaaAlt, 0); + MTL_RELEASE_DEFERRED(m_ptrMsaa); + MTL_RELEASE_DEFERRED(m_ptrStencil); + MTL_RELEASE_DEFERRED(m_ptrAlt); + MTL_RELEASE_DEFERRED(m_ptrMsaaAlt); for (stl::unordered_map::iterator it = m_ptrViews.begin(), itEnd = m_ptrViews.end(); it != itEnd; ++it) { MTL::Texture* view = it->second; - MTL_RELEASE_W(view, 0); + MTL_RELEASE_DEFERRED(view); } m_ptrViews.clear(); } @@ -4729,7 +4769,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames ); MTL_RELEASE(desc, 0); - MTL_RELEASE(tempTexture, 1); + MTL_RELEASE_DEFERRED(tempTexture); } if (NULL != temp) @@ -4958,10 +4998,10 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames SwapChainMtl::~SwapChainMtl() { MTL_RELEASE(m_metalLayer, 2); - MTL_RELEASE(m_drawable, 0); + MTL_RELEASE_DEFERRED(m_drawable); - MTL_RELEASE(m_drawableTexture, 0); - MTL_RELEASE(m_screenshotTarget, 0); + MTL_RELEASE_DEFERRED(m_drawableTexture); + MTL_RELEASE_DEFERRED(m_screenshotTarget); releaseBackBuffer(); } @@ -5130,11 +5170,11 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames } else { - MTL_RELEASE(m_backBufferStencil, m_backBufferDepth == m_backBufferStencil ? 1 : 0); - MTL_RELEASE_W(m_backBufferDepth, 0); + MTL_RELEASE_DEFERRED(m_backBufferStencil); + MTL_RELEASE_DEFERRED(m_backBufferDepth); } - MTL_RELEASE_W(m_backBufferColorMsaa, 0); + MTL_RELEASE_DEFERRED(m_backBufferColorMsaa); } uint32_t SwapChainMtl::resize(const SwapChain& _desc) @@ -5174,6 +5214,33 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames : s_textureFormat[formatColor].fmt) ) ; + const bool hdr10 = 0 != (_desc.flags & BGFX_SWAP_CHAIN_HDR10); + + if (hdr10 + || m_hdr10) + { + if (__builtin_available(macOS 11.0, iOS 16.0, tvOS 16.0, macCatalyst 16.0, VISION_OS_MINIMUM *) ) + { + CFStringRef colorSpaceName = !hdr10 ? NULL + : TextureFormat::RGB10A2 == formatColor ? kCGColorSpaceITUR_2100_PQ + : TextureFormat::RGBA16F == formatColor ? kCGColorSpaceExtendedLinearSRGB + : NULL + ; + + CGColorSpaceRef colorSpace = NULL != colorSpaceName + ? CGColorSpaceCreateWithName(colorSpaceName) + : NULL + ; + + MtlObjAccess::send(m_metalLayer, sel_registerName("setColorspace:"), colorSpace); + MtlObjAccess::send(m_metalLayer, sel_registerName("setWantsExtendedDynamicRangeContent:"), NULL != colorSpace); + + CGColorSpaceRelease(colorSpace); + } + + m_hdr10 = hdr10; + } + const CGSize actualSize = m_metalLayer->drawableSize(); BX_WARN(true && uint32_t(actualSize.width) == _desc.width @@ -5506,18 +5573,38 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames { NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init(); + for (ReleaseArray::iterator it = m_release.begin(), itEnd = m_release.end(); it != itEnd; ++it) + { + MTL_RELEASE_I(it->ptr); + } + + m_release.clear(); + MTL_RELEASE(m_commandQueue, 0); pool->release(); } } + void deferredRelease(NS::Object* _obj) + { + if (NULL != s_renderMtl + && NULL != s_renderMtl->m_cmd.m_commandQueue) + { + s_renderMtl->m_cmd.release(_obj); + } + else + { + _obj->release(); + } + } + MTL::CommandBuffer* CommandQueueMtl::alloc() { { NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init(); - m_activeCommandBuffer = m_commandQueue->commandBuffer(); + m_activeCommandBuffer = m_commandQueue->commandBufferWithUnretainedReferences(); retain(m_activeCommandBuffer); pool->release(); @@ -5531,6 +5618,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames if (queue) { + bx::atomicFetchAndAdd(&queue->m_completedFrames, 1); queue->m_framesSemaphore.post(); } } @@ -5541,7 +5629,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames { if (_endFrame) { - m_releaseWriteIndex = (m_releaseWriteIndex + 1) % BGFX_CONFIG_MAX_FRAME_LATENCY; + ++m_submittedFrames; m_activeCommandBuffer->addCompletedHandler( MTL::HandlerFunction([this](MTL::CommandBuffer*) { commandBufferFinishedCallback(this); }) ); @@ -5588,22 +5676,23 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames void CommandQueueMtl::release(NS::Object* _ptr) { - m_release[m_releaseWriteIndex].push_back(_ptr); + m_release.push_back({ _ptr, m_submittedFrames }); } void CommandQueueMtl::consume() { m_framesSemaphore.wait(); - m_releaseReadIndex = (m_releaseReadIndex + 1) % BGFX_CONFIG_MAX_FRAME_LATENCY; - ResourceArray& ra = m_release[m_releaseReadIndex]; + const uint32_t completedFrames = bx::atomicLoad(&m_completedFrames); - for (ResourceArray::iterator it = ra.begin(), itEnd = ra.end(); it != itEnd; ++it) + ReleaseArray::iterator it = m_release.begin(); + + for (ReleaseArray::iterator itEnd = m_release.end(); it != itEnd && 0 < int32_t(completedFrames - it->frame); ++it) { - MTL_RELEASE_I(*it); + MTL_RELEASE_I(it->ptr); } - ra.clear(); + m_release.erase(m_release.begin(), it); } static constexpr uint64_t kNanosecondsPerSecond = UINT64_C(1000000000); @@ -5681,7 +5770,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames { for (uint32_t ii = 0; ii < BX_COUNTOF(m_sampling); ++ii) { - MTL_RELEASE_W(m_sampling[ii].m_sampleBuffer, 0); + MTL_RELEASE_DEFERRED(m_sampling[ii].m_sampleBuffer); } } @@ -5877,7 +5966,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames void OcclusionQueryMTL::postReset() { - MTL_RELEASE_W(m_buffer, 0); + MTL_RELEASE_DEFERRED(m_buffer); } void OcclusionQueryMTL::preReset() @@ -5894,8 +5983,9 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames Query& query = m_query[m_control.m_current]; query.m_handle = _handle; + query.m_frame = s_renderMtl->m_cmd.m_submittedFrames; uint32_t offset = _handle.idx * 8; - _rce->setVisibilityResultMode( (MTL::VisibilityResultMode)MTL::VisibilityResultModeBoolean, offset); + _rce->setVisibilityResultMode( (MTL::VisibilityResultMode)MTL::VisibilityResultModeCounting, offset); } void OcclusionQueryMTL::end(MTL::RenderCommandEncoder*& _rce) @@ -5908,12 +5998,19 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames void OcclusionQueryMTL::resolve(Frame* _render, bool _wait) { - BX_UNUSED(_wait); + const uint32_t completedFrames = bx::atomicLoad(&s_renderMtl->m_cmd.m_completedFrames); while (0 != m_control.getNumUsed() ) { Query& query = m_query[m_control.m_read]; + if (!_wait + && isValid(query.m_handle) + && int32_t(completedFrames - query.m_frame) <= 0) + { + break; + } + if (isValid(query.m_handle) ) { uint64_t result = ( (uint64_t*)m_buffer->contents() )[query.m_handle.idx]; @@ -6161,7 +6258,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames && (swapChain->m_screenshotTarget->width() != width || swapChain->m_screenshotTarget->height() != height) ) { - MTL_RELEASE(swapChain->m_screenshotTarget, 0); + MTL_RELEASE_DEFERRED(swapChain->m_screenshotTarget); } if (NULL == swapChain->m_screenshotTarget) @@ -6197,7 +6294,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames } else { - MTL_RELEASE(swapChain->m_screenshotTarget, 0); + MTL_RELEASE_DEFERRED(swapChain->m_screenshotTarget); } } diff --git a/src/renderer_mtl.h b/src/renderer_mtl.h index 2eea40fd4..1d2011789 100644 --- a/src/renderer_mtl.h +++ b/src/renderer_mtl.h @@ -72,6 +72,15 @@ #define MTL_RELEASE_W(_obj, _expected) _MTL_RELEASE(_obj, _expected, BX_WARN) #define MTL_RELEASE_I(_obj) _MTL_RELEASE(_obj, 0, BX_NOOP) +#define MTL_RELEASE_DEFERRED(_obj) \ + BX_MACRO_BLOCK_BEGIN \ + if (NULL != _obj) \ + { \ + bgfx::mtl::deferredRelease( (NS::Object*)(void*)_obj); \ + _obj = NULL; \ + } \ + BX_MACRO_BLOCK_END + namespace bgfx { namespace mtl { // Helper to access NS::Object's protected sendMessage for label retrieval @@ -243,6 +252,8 @@ namespace bgfx { namespace mtl return MTL::RasterizationRateMapDescriptor::alloc()->init(); } + void deferredRelease(NS::Object* _obj); + //helper functions template inline void release(T* _obj) @@ -277,7 +288,7 @@ namespace bgfx { namespace mtl void destroy() { - MTL_RELEASE_W(m_ptr, 0); + MTL_RELEASE_DEFERRED(m_ptr); if (NULL != m_dynamic) { @@ -324,13 +335,13 @@ namespace bgfx { namespace mtl { for (FunctionMap::iterator it = m_functions.begin(), itEnd = m_functions.end(); it != itEnd; ++it) { - MTL_RELEASE_W(it->second, 0); + MTL_RELEASE_DEFERRED(it->second); } m_functions.clear(); m_function = NULL; - MTL_RELEASE_W(m_lib, 0); + MTL_RELEASE_DEFERRED(m_lib); } typedef stl::unordered_map FunctionMap; @@ -403,8 +414,8 @@ namespace bgfx { namespace mtl m_fshConstantBuffer = NULL; } - MTL_RELEASE_W(m_rps, 0); - MTL_RELEASE_W(m_cps, 0); + MTL_RELEASE_DEFERRED(m_rps); + MTL_RELEASE_DEFERRED(m_cps); } UniformBuffer* m_vshConstantBuffer; @@ -436,6 +447,17 @@ namespace bgfx { namespace mtl bx::deleteObject(g_allocator, _ptr); } + template + inline void evict(T* _ptr) + { + deferredRelease( (NS::Object*)(void*)_ptr); + } + + inline void evict(PipelineStateMtl* _ptr) + { + release(_ptr); + } + struct VideoDecoderMtl; struct TextureMtl @@ -530,6 +552,7 @@ namespace bgfx { namespace mtl , m_maxAnisotropy(0) , m_colorFormat(TextureFormat::Count) , m_borrowedDepth(false) + , m_hdr10(false) { } @@ -557,6 +580,7 @@ namespace bgfx { namespace mtl void* m_nwh; TextureFormat::Enum m_colorFormat; bool m_borrowedDepth; + bool m_hdr10; }; struct FrameBufferMtl @@ -598,9 +622,9 @@ namespace bgfx { namespace mtl CommandQueueMtl() : m_commandQueue(NULL) , m_activeCommandBuffer(NULL) - , m_releaseWriteIndex(0) - , m_releaseReadIndex(0) , m_maxFrameLatency(BGFX_CONFIG_MAX_FRAME_LATENCY) + , m_submittedFrames(0) + , m_completedFrames(0) { } @@ -621,11 +645,17 @@ namespace bgfx { namespace mtl MTL::CommandQueue* m_commandQueue; MTL::CommandBuffer* m_activeCommandBuffer; - int m_releaseWriteIndex; - int m_releaseReadIndex; uint32_t m_maxFrameLatency; - typedef stl::vector ResourceArray; - ResourceArray m_release[BGFX_CONFIG_MAX_FRAME_LATENCY]; + uint32_t m_submittedFrames; + volatile uint32_t m_completedFrames; + struct Release + { + NS::Object* ptr; + uint32_t frame; + }; + + typedef stl::vector ReleaseArray; + ReleaseArray m_release; }; struct TimerQueryMtl @@ -720,6 +750,7 @@ namespace bgfx { namespace mtl struct Query { OcclusionQueryHandle m_handle; + uint32_t m_frame; }; MTL::Buffer* m_buffer; @@ -736,7 +767,7 @@ namespace bgfx { static void evict(Ty* _ptr) { - mtl::release(_ptr); + mtl::evict(_ptr); } static void validate(Ty* /*_ptr*/, uint64_t /*_key*/)