diff --git a/examples/25-c99/helloworld.c b/examples/25-c99/helloworld.c index 9fa0a576c..072e484fc 100644 --- a/examples/25-c99/helloworld.c +++ b/examples/25-c99/helloworld.c @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 Branimir Karadzic. All rights reserved. + * Copyright 2011-2025 Branimir Karadzic. All rights reserved. * License: http://www.opensource.org/licenses/BSD-2-Clause */ diff --git a/examples/50-headless/headless.cpp b/examples/50-headless/headless.cpp new file mode 100644 index 000000000..39786bf59 --- /dev/null +++ b/examples/50-headless/headless.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2011-2025 Branimir Karadzic. All rights reserved. + * License: http://www.opensource.org/licenses/BSD-2-Clause + */ + +#include +#include +#include +#include + +struct PosColorVertex +{ + float m_x; + float m_y; + float m_z; + uint32_t m_abgr; + + static void init() + { + ms_layout + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) + .end(); + }; + + static bgfx::VertexLayout ms_layout; +}; + +bgfx::VertexLayout PosColorVertex::ms_layout; + +static PosColorVertex s_cubeVertices[] = +{ + {-1.0f, 1.0f, 1.0f, 0xff000000 }, + { 1.0f, 1.0f, 1.0f, 0xff0000ff }, + {-1.0f, -1.0f, 1.0f, 0xff00ff00 }, + { 1.0f, -1.0f, 1.0f, 0xff00ffff }, + {-1.0f, 1.0f, -1.0f, 0xffff0000 }, + { 1.0f, 1.0f, -1.0f, 0xffff00ff }, + {-1.0f, -1.0f, -1.0f, 0xffffff00 }, + { 1.0f, -1.0f, -1.0f, 0xffffffff }, +}; + +static const uint16_t s_cubeTriList[] = +{ + 0, 1, 2, // 0 + 1, 3, 2, + 4, 6, 5, // 2 + 5, 6, 7, + 0, 2, 4, // 4 + 4, 2, 6, + 1, 5, 3, // 6 + 5, 7, 3, + 0, 4, 1, // 8 + 4, 5, 1, + 2, 3, 6, // 10 + 6, 3, 7, +}; + +int _main_(int _argc, char** _argv) +{ + bx::printf( + "\n" + "\n" + "\tThis example demonstrates headless initialization.\n" + "\n" + ); + + Args args(_argc, _argv); + + bgfx::Init init; + init.type = args.m_type; + init.vendorId = args.m_pciId; + init.resolution.width = 0; + init.resolution.height = 0; + + if (!bgfx::init(init) ) + { + bx::printf( + "\t - Failed to initialize headless mode!\n" + "\n" + ); + return bx::kExitFailure; + } + + bx::printf( + "\t - Headless mode initialized successfuly!\n" + "\n" + ); + + constexpr uint32_t kWidth = 1280; + constexpr uint32_t kHeight = 720; + + // Create vertex stream declaration. + PosColorVertex::init(); + + // Create static vertex buffer. + bgfx::VertexBufferHandle vbh = bgfx::createVertexBuffer( + // Static data can be passed with bgfx::makeRef + bgfx::makeRef(s_cubeVertices, sizeof(s_cubeVertices) ) + , PosColorVertex::ms_layout + ); + + // Create static index buffer for triangle list rendering. + bgfx::IndexBufferHandle ibh = bgfx::createIndexBuffer( + // Static data can be passed with bgfx::makeRef + bgfx::makeRef(s_cubeTriList, sizeof(s_cubeTriList) ) + ); + + bgfx::ProgramHandle program = loadProgram("vs_cubes", "fs_cubes"); + + bgfx::FrameBufferHandle fbh = bgfx::createFrameBuffer( + kWidth + , kHeight + , bgfx::TextureFormat::BGRA8 + ); + + bgfx::TextureHandle rb = bgfx::createTexture2D( + kWidth + , kHeight + , false + , 1 + , bgfx::TextureFormat::BGRA8 + , BGFX_TEXTURE_BLIT_DST|BGFX_TEXTURE_READ_BACK + ); + + // Set view 0 clear state. + bgfx::setViewClear(0 + , BGFX_CLEAR_COLOR|BGFX_CLEAR_DEPTH + , 0x303030ff + , 1.0f + , 0 + ); + + constexpr uint64_t state = 0 + | BGFX_STATE_WRITE_RGB + | BGFX_STATE_WRITE_A + | BGFX_STATE_WRITE_Z + | BGFX_STATE_DEPTH_TEST_LESS + | BGFX_STATE_CULL_CW + | BGFX_STATE_MSAA + ; + + const uint64_t timeOffset = bx::getHPCounter(); + + uint32_t currentFrame = 0; + + bx::printf( + "\t - Rendering into offscreen framebuffer.\n" + "\n" + ); + + for (uint32_t ii = 0; ii < 5; ++ii) + { + const float time = (float)( (bx::getHPCounter()-timeOffset)/double(bx::getHPFrequency() ) ); + + const bx::Vec3 at = { 0.0f, 0.0f, 0.0f }; + const bx::Vec3 eye = { 0.0f, 0.0f, -35.0f }; + + // Set view and projection matrix for view 0. + { + float view[16]; + bx::mtxLookAt(view, eye, at); + + float proj[16]; + bx::mtxProj(proj, 60.0f, float(kWidth)/float(kHeight), 0.1f, 100.0f, bgfx::getCaps()->homogeneousDepth); + bgfx::setViewTransform(0, view, proj); + + // Set view 0 default viewport. + bgfx::setViewRect(0, 0, 0, uint16_t(kWidth), uint16_t(kHeight) ); + bgfx::setViewFrameBuffer(0, fbh); + } + + // Submit 11x11 cubes. + for (uint32_t yy = 0; yy < 11; ++yy) + { + for (uint32_t xx = 0; xx < 11; ++xx) + { + float mtx[16]; + bx::mtxRotateXY(mtx, time + xx*0.21f, time + yy*0.37f); + mtx[12] = -15.0f + float(xx)*3.0f; + mtx[13] = -15.0f + float(yy)*3.0f; + mtx[14] = 0.0f; + + // Set model matrix for rendering. + bgfx::setTransform(mtx); + + // Set vertex and index buffer. + bgfx::setVertexBuffer(0, vbh); + bgfx::setIndexBuffer(ibh); + + // Set render states. + bgfx::setState(state); + + // Submit primitive for rendering to view 0. + bgfx::submit(0, program); + } + } + + bgfx::blit(1, rb, 0, 0, bgfx::getTexture(fbh) ); + + currentFrame = bgfx::frame(); + } + + bx::FilePath filePath(bx::Dir::Current); + filePath.join("temp/headless.tga"); + + bx::FileWriter writer; + bx::Error err; + if (bx::open(&writer, filePath, false, &err) ) + { + bx::DefaultAllocator allocator; + + uint8_t* data = (uint8_t*)bx::alloc(&allocator, kWidth*kHeight*4); + + uint32_t expectedFrame = bgfx::readTexture(rb, data); + + while (currentFrame < expectedFrame) // Make sure read texture is complete. + { + currentFrame = bgfx::frame(); + } + + bimg::imageWriteTga(&writer, kWidth, kHeight, kWidth*4, data, false, false, &err); + + bx::free(&allocator, data); + } + + bx::printf( + "\t - Screenshot written into:\n" + "\t %s\n" + "\n" + "\t - Shuting it down!\n" + "\n" + , filePath.getCPtr() + ); + + bgfx::destroy(rb); + bgfx::destroy(fbh); + bgfx::destroy(program); + bgfx::destroy(ibh); + bgfx::destroy(vbh); + + bgfx::shutdown(); + + bx::printf( + "\t - Exiting.\n" + "\n" + "\n" + ); + + return bx::kExitSuccess; +} diff --git a/examples/common/args.h b/examples/common/args.h new file mode 100644 index 000000000..c41757522 --- /dev/null +++ b/examples/common/args.h @@ -0,0 +1,66 @@ +/* + * Copyright 2011-2025 Branimir Karadzic. All rights reserved. + * License: http://www.opensource.org/licenses/BSD-2-Clause + */ + +#include +#include + +/// +struct Args +{ + Args(int _argc, const char* const* _argv) + : m_type(bgfx::RendererType::Count) + , m_pciId(BGFX_PCI_ID_NONE) + { + bx::CommandLine cmdLine(_argc, (const char**)_argv); + + if (cmdLine.hasArg("gl") ) + { + m_type = bgfx::RendererType::OpenGL; + } + else if (cmdLine.hasArg("vk") ) + { + m_type = bgfx::RendererType::Vulkan; + } + else if (cmdLine.hasArg("noop") ) + { + m_type = bgfx::RendererType::Noop; + } + else if (cmdLine.hasArg("d3d11") ) + { + m_type = bgfx::RendererType::Direct3D11; + } + else if (cmdLine.hasArg("d3d12") ) + { + m_type = bgfx::RendererType::Direct3D12; + } + else if (BX_ENABLED(BX_PLATFORM_OSX) ) + { + if (cmdLine.hasArg("mtl") ) + { + m_type = bgfx::RendererType::Metal; + } + } + + if (cmdLine.hasArg("amd") ) + { + m_pciId = BGFX_PCI_ID_AMD; + } + else if (cmdLine.hasArg("nvidia") ) + { + m_pciId = BGFX_PCI_ID_NVIDIA; + } + else if (cmdLine.hasArg("intel") ) + { + m_pciId = BGFX_PCI_ID_INTEL; + } + else if (cmdLine.hasArg("sw") ) + { + m_pciId = BGFX_PCI_ID_SOFTWARE_RASTERIZER; + } + } + + bgfx::RendererType::Enum m_type; + uint16_t m_pciId; +}; diff --git a/examples/common/bgfx_utils.cpp b/examples/common/bgfx_utils.cpp index baaeba382..944434d8f 100644 --- a/examples/common/bgfx_utils.cpp +++ b/examples/common/bgfx_utils.cpp @@ -746,55 +746,3 @@ bgfx::RendererType::Enum getType(const bx::StringView& _name) return bgfx::RendererType::Count; } - -Args::Args(int _argc, const char* const* _argv) - : m_type(bgfx::RendererType::Count) - , m_pciId(BGFX_PCI_ID_NONE) -{ - bx::CommandLine cmdLine(_argc, (const char**)_argv); - - if (cmdLine.hasArg("gl") ) - { - m_type = bgfx::RendererType::OpenGL; - } - else if (cmdLine.hasArg("vk") ) - { - m_type = bgfx::RendererType::Vulkan; - } - else if (cmdLine.hasArg("noop") ) - { - m_type = bgfx::RendererType::Noop; - } - else if (cmdLine.hasArg("d3d11") ) - { - m_type = bgfx::RendererType::Direct3D11; - } - else if (cmdLine.hasArg("d3d12") ) - { - m_type = bgfx::RendererType::Direct3D12; - } - else if (BX_ENABLED(BX_PLATFORM_OSX) ) - { - if (cmdLine.hasArg("mtl") ) - { - m_type = bgfx::RendererType::Metal; - } - } - - if (cmdLine.hasArg("amd") ) - { - m_pciId = BGFX_PCI_ID_AMD; - } - else if (cmdLine.hasArg("nvidia") ) - { - m_pciId = BGFX_PCI_ID_NVIDIA; - } - else if (cmdLine.hasArg("intel") ) - { - m_pciId = BGFX_PCI_ID_INTEL; - } - else if (cmdLine.hasArg("sw") ) - { - m_pciId = BGFX_PCI_ID_SOFTWARE_RASTERIZER; - } -} diff --git a/examples/common/bgfx_utils.h b/examples/common/bgfx_utils.h index 2e9dc7246..fb468431f 100644 --- a/examples/common/bgfx_utils.h +++ b/examples/common/bgfx_utils.h @@ -16,6 +16,7 @@ #include namespace stl = tinystl; +#include "args.h" /// void* load(const bx::FilePath& _filePath, uint32_t* _size = NULL); @@ -152,13 +153,4 @@ bx::StringView getName(bgfx::RendererType::Enum _type); /// Name to bgfx::RendererType::Enum. bgfx::RendererType::Enum getType(const bx::StringView& _name); -/// -struct Args -{ - Args(int _argc, const char* const* _argv); - - bgfx::RendererType::Enum m_type; - uint16_t m_pciId; -}; - #endif // BGFX_UTILS_H_HEADER_GUARD diff --git a/scripts/genie.lua b/scripts/genie.lua index ca14c8ae4..517225685 100644 --- a/scripts/genie.lua +++ b/scripts/genie.lua @@ -410,13 +410,17 @@ function exampleProjectDefaults() strip() end -function exampleProject(_combined, ...) +function exampleProject(_combined, _consoleApp, ...) if _combined then project ("examples") uuid (os.uuid("examples")) - kind "WindowedApp" + if _consoleApp then + kind "ConsoleApp" + else + kind "WindowedApp" + end for _, name in ipairs({...}) do @@ -441,9 +445,14 @@ function exampleProject(_combined, ...) else for _, name in ipairs({...}) do + project ("example-" .. name) uuid (os.uuid("example-" .. name)) - kind "WindowedApp" + if _consoleApp then + kind "ConsoleApp" + else + kind "WindowedApp" + end files { path.join(BGFX_DIR, "examples", name, "**.c"), @@ -508,7 +517,7 @@ if _OPTIONS["with-examples"] or _OPTIONS["with-combined-examples"] then group "examples" - exampleProject(_OPTIONS["with-combined-examples"] + exampleProject(_OPTIONS["with-combined-examples"], false , "00-helloworld" , "01-cubes" , "02-metaballs" @@ -559,14 +568,15 @@ or _OPTIONS["with-combined-examples"] then , "49-hextile" ) - -- 17-drawstress requires multithreading, does not compile for singlethreaded wasm + if premake.gcc.namestyle == nil or not premake.gcc.namestyle == "Emscripten" then - exampleProject(false, "17-drawstress") + exampleProject(false, false, "17-drawstress") -- 17-drawstress requires multithreading, does not compile for singlethreaded wasm + exampleProject(false, true, "50-headless") -- 50-headless is not tested with emscripten end -- C99 source doesn't compile under WinRT settings if not premake.vstudio.iswinrt() then - exampleProject(false, "25-c99") + exampleProject(false, false, "25-c99") end end diff --git a/src/renderer_mtl.mm b/src/renderer_mtl.mm index e0521d5eb..7f262fea3 100644 --- a/src/renderer_mtl.mm +++ b/src/renderer_mtl.mm @@ -808,23 +808,30 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames m_vertexDescriptor = newVertexDescriptor(); m_samplerDescriptor = newSamplerDescriptor(); - m_mainFrameBuffer.create( - 0 - , g_platformData.nwh - , m_resolution.width - , m_resolution.height - , m_resolution.formatColor - , m_resolution.formatDepthStencil - ); - m_textVideoMem.resize(false, m_resolution.width, m_resolution.height); - m_textVideoMem.clear(); - - m_numWindows = 1; - - if (NULL == m_mainFrameBuffer.m_swapChain->m_metalLayer) + if (NULL == g_platformData.nwh) { - MTL_RELEASE(m_device, 0); - return false; + BX_TRACE("Headless."); + } + else + { + m_mainFrameBuffer.create( + 0 + , g_platformData.nwh + , m_resolution.width + , m_resolution.height + , m_resolution.formatColor + , m_resolution.formatDepthStencil + ); + m_textVideoMem.resize(false, m_resolution.width, m_resolution.height); + m_textVideoMem.clear(); + + m_numWindows = 1; + + if (NULL == m_mainFrameBuffer.m_swapChain->m_metalLayer) + { + MTL_RELEASE(m_device, 0); + return false; + } } m_cmd.init(m_device); @@ -1064,9 +1071,12 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames { } - MTLPixelFormat getSwapChainPixelFormat(SwapChainMtl *swapChain) + static MTLPixelFormat getSwapChainPixelFormat(SwapChainMtl* _swapChain) { - return swapChain->m_metalLayer.pixelFormat; + return NULL != _swapChain + ? _swapChain->m_metalLayer.pixelFormat + : kMtlPixelFormatInvalid + ; } void readTexture(TextureHandle _handle, void* _data, uint8_t _mip) override @@ -1466,10 +1476,15 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames void updateResolution(const Resolution& _resolution) { - m_mainFrameBuffer.m_swapChain->m_maxAnisotropy = !!(_resolution.reset & BGFX_RESET_MAXANISOTROPY) - ? 16 - : 1 - ; + SwapChainMtl* swapChain = m_mainFrameBuffer.m_swapChain; + + if (NULL != swapChain) + { + swapChain->m_maxAnisotropy = !!(_resolution.reset & BGFX_RESET_MAXANISOTROPY) + ? 16 + : 1 + ; + } const uint32_t maskFlags = ~(0 | BGFX_RESET_MAXANISOTROPY @@ -1481,19 +1496,23 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames || m_resolution.height != _resolution.height || (m_resolution.reset&maskFlags) != (_resolution.reset&maskFlags) ) { - MTLPixelFormat prevMetalLayerPixelFormat = getSwapChainPixelFormat(m_mainFrameBuffer.m_swapChain); m_resolution = _resolution; - if (m_resolution.reset & BGFX_RESET_INTERNAL_FORCE - && m_mainFrameBuffer.m_swapChain->m_nwh != g_platformData.nwh) + const MTLPixelFormat prevPixelFormat = getSwapChainPixelFormat(swapChain); + + if (NULL != swapChain) { - m_mainFrameBuffer.m_swapChain->init(g_platformData.nwh); + if (m_resolution.reset & BGFX_RESET_INTERNAL_FORCE + && swapChain->m_nwh != g_platformData.nwh) + { + swapChain->init(g_platformData.nwh); + } + + m_mainFrameBuffer.resizeSwapChain(_resolution.width, _resolution.height); } m_resolution.reset &= ~BGFX_RESET_INTERNAL_FORCE; - m_mainFrameBuffer.resizeSwapChain(_resolution.width, _resolution.height); - for (uint32_t ii = 0; ii < BX_COUNTOF(m_frameBuffers); ++ii) { m_frameBuffers[ii].postReset(); @@ -1504,12 +1523,14 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames m_textVideoMem.resize(false, _resolution.width, _resolution.height); m_textVideoMem.clear(); - if (prevMetalLayerPixelFormat != getSwapChainPixelFormat(m_mainFrameBuffer.m_swapChain) ) + const MTLPixelFormat pixelFormat = getSwapChainPixelFormat(swapChain); + + if (prevPixelFormat != pixelFormat) { MTL_RELEASE_I(m_screenshotBlitRenderPipelineState); reset(m_renderPipelineDescriptor); - m_renderPipelineDescriptor.colorAttachments[0].pixelFormat = getSwapChainPixelFormat(m_mainFrameBuffer.m_swapChain); + m_renderPipelineDescriptor.colorAttachments[0].pixelFormat = pixelFormat; m_renderPipelineDescriptor.vertexFunction = m_screenshotBlitProgram.m_vsh->m_function; m_renderPipelineDescriptor.fragmentFunction = m_screenshotBlitProgram.m_fsh->m_function; m_screenshotBlitRenderPipelineState = m_device.newRenderPipelineStateWithDescriptor(m_renderPipelineDescriptor); diff --git a/src/renderer_vk.cpp b/src/renderer_vk.cpp index da17f4785..d9debd966 100644 --- a/src/renderer_vk.cpp +++ b/src/renderer_vk.cpp @@ -2446,11 +2446,6 @@ VK_IMPORT_DEVICE { FrameBufferVK& frameBuffer = m_frameBuffers[_handle.idx]; - if (_handle.idx == m_fbh.idx) - { - setFrameBuffer(BGFX_INVALID_HANDLE, false); - } - uint16_t denseIdx = frameBuffer.destroy(); if (UINT16_MAX != denseIdx) { @@ -5966,20 +5961,14 @@ VK_DESTROY return; } - uint32_t mipHeight = bx::uint32_max(1, m_height >> _mip); - uint32_t rowPitch = pitch(_mip); + const uint32_t mipHeight = bx::uint32_max(1, m_height >> _mip); + const uint32_t rowPitch = pitch(_mip); - uint8_t* src; + const uint8_t* src; VK_CHECK(vkMapMemory(s_renderVK->m_device, _memory, 0, VK_WHOLE_SIZE, 0, (void**)&src) ); src += _offset; - uint8_t* dst = (uint8_t*)_data; - for (uint32_t yy = 0; yy < mipHeight; ++yy) - { - bx::memCopy(dst, src, rowPitch); - src += rowPitch; - dst += rowPitch; - } + bx::gather(_data, src, rowPitch, rowPitch, mipHeight); vkUnmapMemory(s_renderVK->m_device, _memory); }