Pārlūkot izejas kodu

Added 50-headless example.

Бранимир Караџић 1 mēnesi atpakaļ
vecāks
revīzija
3722e21525

+ 1 - 1
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
  */
 

+ 252 - 0
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 <entry/entry.h>
+#include <bgfx_utils.h>
+#include <bx/timer.h>
+#include <bx/file.h>
+
+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;
+}

+ 66 - 0
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 <bgfx/bgfx.h>
+#include <bx/commandline.h>
+
+///
+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;
+};

+ 0 - 52
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;
-	}
-}

+ 1 - 9
examples/common/bgfx_utils.h

@@ -16,6 +16,7 @@
 #include <tinystl/vector.h>
 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

+ 17 - 7
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
 

+ 50 - 29
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();
+			if (NULL == g_platformData.nwh)
+			{
+				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;
+				m_numWindows = 1;
 
-			if (NULL == m_mainFrameBuffer.m_swapChain->m_metalLayer)
-			{
-				MTL_RELEASE(m_device, 0);
-				return false;
+				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);

+ 4 - 15
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);
 	}