ソースを参照

Adds example 48-multidrawindirect (#2894)

* Added example 48-multidrawindirect

* Added example 48-multidrawindirect

* enable shader.mk to build directories that only have compute shaders

* compiled shaders for example 48

* MultidrawIndirect example renamed to DrawIndirect, sets draw mtx in compute

* update 48-drawindirect shaders

* fix typos

* fixed bgfx::setPlatformData change for example48

* fix docs

* added name to cs_drawindirect.sc

* style updates for example 44

* add slider to example 48-drawindirect
SnapperTT 3 年 前
コミット
135dc7fa27

+ 24 - 0
docs/examples.rst

@@ -665,3 +665,27 @@ from lower resolution inputs.
 
 .. figure:: https://github.com/bkaradzic/bgfx/raw/master/examples/46-fsr/screenshot.png
    :alt: example-46-fsr
+
+`47-Pixel Formats <https://github.com/bkaradzic/bgfx/tree/master/examples/47-pixelformats>`__
+--------------------------------------------------------------------------
+
+Pixel Formats
+
+View and test texture formats
+
+.. figure:: https://github.com/bkaradzic/bgfx/raw/master/examples/47-pixelformats/screenshot.png
+   :alt: example-47-pixelformats
+
+`48-drawindirect  <https://github.com/bkaradzic/bgfx/tree/master/examples/48-drawindirect>`__
+--------------------------------------------------------------------------
+
+Draw Indirect
+
+Simple example of indirect rendering + an implementation of multidraw indirect
+
+Reference(s):
+ - `OpenGL MultiDrawIndirect with per-instance textures <https://web.archive.org/web/20201109155619/https://litasa.github.io/blog/2017/09/04/OpenGL-MultiDrawIndirect-with-Individual-Textures>`__.
+ - `Indirect Rendering - A way to a million draw calls <https://web.archive.org/web/20210926073337/https://cpp-rendering.io/indirect-rendering>`__.
+
+.. figure:: https://github.com/bkaradzic/bgfx/raw/master/examples/48-drawindirect/screenshot.png
+   :alt: example-48-drawindirect

+ 78 - 0
examples/48-drawindirect/cs_drawindirect.sc

@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 Liam Twigger. All rights reserved.
+ * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
+ */
+
+#include "bgfx_compute.sh"
+
+//instance data for all instances (pre culling)
+BUFFER_RO(instanceDataIn, vec4, 0);
+
+// Output
+BUFFER_WR(indirectBuffer, uvec4, 1);
+BUFFER_WR(instanceBufferOut, vec4, 2);
+
+uniform vec4 u_drawParams;
+
+NUM_THREADS(1, 1, 1)
+
+void main()
+{
+	int numDrawItems = int(u_drawParams.x);
+	int sideSize = int(u_drawParams.y);
+	float time = u_drawParams.z;
+	
+	// Prepare draw mtx
+	
+	int maxToDraw = min(sideSize*sideSize, numDrawItems);
+	
+	for (int k = 0; k < maxToDraw; k++) {
+		int yy = k / sideSize;
+		int xx = k % sideSize;
+
+		float _ax = time + xx * 0.21f;
+		float _ay = time + yy * 0.37f;
+		float sx = sin(_ax);
+		float cx = cos(_ax);
+		float sy = sin(_ay);
+		float cy = cos(_ay);
+
+		vec4 a = vec4(    cy,  0,     sy, 0);
+		vec4 b = vec4( sx*sy, cx, -sx*cy, 0);
+		vec4 c = vec4(-cx*sy, sx,  cx*cy, 0);
+		
+		vec4 d = vec4(-15.0f - (sideSize-11)*1.2f + float(xx) * 3.0f, -15.0f - (sideSize-11)*1.4f + float(yy) * 3.0f, max(0.0f, (sideSize-11.0f)*3.0f), 1.0f);
+		
+		vec4 color;
+		color.x = sin(time + float(xx) / 11.0f) * 0.5f + 0.5f;
+		color.y = cos(time + float(yy) / 11.0f) * 0.5f + 0.5f;
+		color.z = sin(time * 3.0f) * 0.5f + 0.5f;
+		color.w = 1.0f;
+		
+		instanceBufferOut[k*5+0] = a;
+		instanceBufferOut[k*5+1] = b;
+		instanceBufferOut[k*5+2] = c;
+		instanceBufferOut[k*5+3] = d;
+		instanceBufferOut[k*5+4] = color;
+		}
+	
+	// Fill indirect buffer
+	
+	for (int k = 0; k < maxToDraw; k++) {
+		drawIndexedIndirect(
+						// Target location params:
+			indirectBuffer,			// target buffer
+			k,						// index in buffer
+						// Draw call params:
+			instanceDataIn[k].w,	// number of indices for this draw call
+			1u, 					// number of instances for this draw call. You can disable this draw call by setting to zero
+			instanceDataIn[k].z,	// offset in the index buffer
+			instanceDataIn[k].x,	// offset in the vertex buffer. Note that you can use this to "reindex" submeshses - all indicies in this draw will be decremented by this amount
+			k						// offset in the instance buffer. If you are drawing more than 1 instance per call see gpudrivenrendering for how to handle
+			);
+		}
+
+}
+
+
+

+ 413 - 0
examples/48-drawindirect/drawindirect.cpp

@@ -0,0 +1,413 @@
+/*
+ * Copyright 2022-2022 Liam Twigger. All rights reserved.
+ * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
+ */
+
+// Draw Indirect
+// Render multiple different objects with one draw command. This example shows a minimal implementation of indirect drawing
+// Reading References:
+// https://litasa.github.io/blog/2017/09/04/OpenGL-MultiDrawIndirect-with-Individual-Textures
+// https://cpp-rendering.io/indirect-rendering/
+
+#include "common.h"
+#include "bgfx_utils.h"
+#include "imgui/imgui.h"
+
+namespace
+{
+
+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;
+
+struct ObjectInstance {
+	float m_vertexOffset;
+	float m_vertexCount;
+	float m_indexOffset;
+	float m_indexCount;
+	
+	static void init()
+	{
+		ms_layout
+			.begin()
+			.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
+			.end();
+	};
+	
+	static bgfx::VertexLayout ms_layout;
+	};
+
+bgfx::VertexLayout ObjectInstance::ms_layout;
+
+struct RenderInstance {
+	float m_mtx[16];
+	float m_color[4];
+	
+	static void init()
+	{
+		ms_layout
+			.begin()
+			.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
+			.add(bgfx::Attrib::TexCoord1, 4, bgfx::AttribType::Float)
+			.add(bgfx::Attrib::TexCoord2, 4, bgfx::AttribType::Float)
+			.add(bgfx::Attrib::TexCoord3, 4, bgfx::AttribType::Float)
+			.add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Float)
+			.end();
+	};
+	
+	static bgfx::VertexLayout ms_layout;
+	};
+	
+bgfx::VertexLayout RenderInstance::ms_layout;
+
+static PosColorVertex s_multiMeshVertices[12] =
+{
+	// Cube Model
+	{-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 },
+	
+	// Tetrahedron Model (offset = 8)
+	{ 1.0f,  1.0f,  1.0f, 0xff0000ff },
+	{ 1.0f, -1.0f, -1.0f, 0xff000000 },
+	{-1.0f,  1.0f, -1.0f, 0xff00ff00 },
+	{-1.0f, -1.0f,  1.0f, 0xff00ffff },
+};
+
+static const uint16_t s_multiMeshIndices[48] =
+{
+	// Cube Indicies
+	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,
+	
+	// Tetrahedron Indices (offset = 36)
+	0, 2, 1,
+	1, 2, 3,
+	0, 3, 2,
+	1, 3, 0,
+};
+
+static const uint32_t s_maxSideSize = 81;
+
+class DrawIndirect : public entry::AppI
+{
+public:
+	DrawIndirect(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_TEXT;
+		m_reset  = BGFX_RESET_VSYNC;
+		m_sideSize         = 11;
+		m_nDrawElements = s_maxSideSize*s_maxSideSize;
+
+		bgfx::Init init;
+		init.type     = args.m_type;
+		init.vendorId = args.m_pciId;
+		init.platformData.nwh  = entry::getNativeWindowHandle(entry::kDefaultWindowHandle);
+		init.platformData.ndt  = entry::getNativeDisplayHandle();
+		init.resolution.width  = m_width;
+		init.resolution.height = m_height;
+		init.resolution.reset  = m_reset;
+		bgfx::init(init);
+
+		// Enable debug text.
+		bgfx::setDebug(m_debug);
+
+		// Set view 0 clear state.
+		bgfx::setViewClear(0
+			, BGFX_CLEAR_COLOR|BGFX_CLEAR_DEPTH
+			, 0x303030ff
+			, 1.0f
+			, 0
+			);
+
+		// Create vertex stream declaration.
+		PosColorVertex::init();
+		ObjectInstance::init();
+		RenderInstance::init();
+
+		// Create static vertex buffer.
+		m_vbh = bgfx::createVertexBuffer(
+					  bgfx::makeRef(s_multiMeshVertices, sizeof(s_multiMeshVertices) )
+					, PosColorVertex::ms_layout
+					);
+
+		// Create static index buffer.
+		m_ibh = bgfx::createIndexBuffer(
+					bgfx::makeRef(s_multiMeshIndices, sizeof(s_multiMeshIndices) )
+					);
+
+		// Create program from shaders.
+		m_program = loadProgram("vs_instancing", "fs_instancing"); // These are reused from 05-instancing
+		
+		m_indirect_program = BGFX_INVALID_HANDLE;
+		m_indirect_buffer_handle = BGFX_INVALID_HANDLE;
+		m_object_list_buffer = BGFX_INVALID_HANDLE;
+		
+		u_drawParams = bgfx::createUniform("u_drawParams", bgfx::UniformType::Vec4);
+		
+		const bool computeSupported = !!(BGFX_CAPS_DRAW_INDIRECT & bgfx::getCaps()->supported);
+		const bool instancingSupported = !!(BGFX_CAPS_INSTANCING & bgfx::getCaps()->supported);
+		
+		if (computeSupported && instancingSupported)
+		{
+			// Set up indirect program
+			// This is a barebones program that populates the indirect buffer handle with draw requests
+			m_indirect_program = bgfx::createProgram(loadShader("cs_drawindirect"), true);
+			m_indirect_buffer_handle = bgfx::createIndirectBuffer(m_nDrawElements);
+			
+			const bgfx::Memory * mem = bgfx::alloc(sizeof(ObjectInstance) * m_nDrawElements);
+			ObjectInstance* objs = (ObjectInstance*) mem->data;
+			
+			for (uint32_t ii = 0; ii < m_nDrawElements; ++ii)
+			{
+				if (ii % 2)
+				{
+					// Tetrahedron
+					objs[ii].m_vertexOffset = 8;
+					objs[ii].m_vertexCount = 4; // m_vertexCount is unused in compute shader, its only here for completeness
+					objs[ii].m_indexOffset = 36;
+					objs[ii].m_indexCount = 12;
+				}
+				else
+				{
+					// Cube
+					objs[ii].m_vertexOffset = 0;
+					objs[ii].m_vertexCount = 8;
+					objs[ii].m_indexOffset = 0;
+					objs[ii].m_indexCount = 36;
+				}
+			}
+			
+			// This is a list of objects to be rendered via the indirect program
+			m_object_list_buffer = bgfx::createVertexBuffer(mem, ObjectInstance::ms_layout, BGFX_BUFFER_COMPUTE_READ);
+			
+			// This is the instance buffer used for rendering. 
+			// You could instead use a dynamic instance buffer when rendering (use bgfx::allocInstanceDataBuffer in draw loop)
+			m_instance_buffer = bgfx::createDynamicVertexBuffer(m_nDrawElements, RenderInstance::ms_layout, BGFX_BUFFER_COMPUTE_WRITE);
+		}
+			
+		m_timeOffset = bx::getHPCounter();
+
+		imguiCreate();
+	}
+
+	int shutdown() override
+	{
+		imguiDestroy();
+
+		// Cleanup.
+		bgfx::destroy(m_ibh);
+		bgfx::destroy(m_vbh);
+		bgfx::destroy(m_program);
+
+		if (bgfx::isValid(m_indirect_program))
+		{
+			bgfx::destroy(m_indirect_program);
+		}
+		if (bgfx::isValid(m_indirect_buffer_handle))
+		{
+			bgfx::destroy(m_indirect_buffer_handle);
+		}
+		if (bgfx::isValid(m_object_list_buffer))
+		{
+			bgfx::destroy(m_object_list_buffer);
+		}
+		if (bgfx::isValid(m_instance_buffer))
+		{
+			bgfx::destroy(m_instance_buffer);
+		}
+		bgfx::destroy(u_drawParams);
+		
+		// Shutdown bgfx.
+		bgfx::shutdown();
+
+		return 0;
+	}
+
+	bool update() override
+	{
+		if (!entry::processEvents(m_width, m_height, m_debug, m_reset, &m_mouseState) )
+		{
+			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);
+
+			ImGui::SetNextWindowPos(
+				ImVec2(m_width - m_width / 5.0f - 10.0f, 10.0f)
+				, ImGuiCond_FirstUseEver
+			);
+			ImGui::SetNextWindowSize(
+				ImVec2(m_width / 5.0f, m_height / 2.0f)
+				, ImGuiCond_FirstUseEver
+			);
+			ImGui::Begin("Settings"
+				, NULL
+				, 0
+			);
+
+			ImGui::Text("%d draw calls", bgfx::getStats()->numDraw);
+			ImGui::Text("%d objects drawn", m_sideSize*m_sideSize);
+
+			ImGui::Text("Grid Side Size:");
+			ImGui::SliderInt("##size", (int*)&m_sideSize, 1, s_maxSideSize);
+
+			ImGui::End();
+			
+			imguiEndFrame();
+			
+
+			// Set view 0 default viewport.
+			bgfx::setViewRect(0, 0, 0, uint16_t(m_width), uint16_t(m_height) );
+
+			// This dummy draw call is here to make sure that view 0 is cleared
+			// if no other draw calls are submitted to view 0.
+			bgfx::touch(0);
+
+			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(m_width)/float(m_height), 0.1f, 500.0f, bgfx::getCaps()->homogeneousDepth);
+				bgfx::setViewTransform(0, view, proj);
+
+				// Set view 0 default viewport.
+				bgfx::setViewRect(0, 0, 0, uint16_t(m_width), uint16_t(m_height) );
+			}
+			
+			float time = (float)( (bx::getHPCounter() - m_timeOffset)/double(bx::getHPFrequency() ) );
+
+			// Get renderer capabilities info.
+			const bool computeSupported = !!(BGFX_CAPS_DRAW_INDIRECT & bgfx::getCaps()->supported);
+			const bool instancingSupported = !!(BGFX_CAPS_INSTANCING & bgfx::getCaps()->supported);
+			
+			if (computeSupported && instancingSupported)
+			{
+				// Build indirect buffer & prepare instance buffer
+				// NOTE: IF you are rendering static data then
+				// this could be done once on startup and results stored
+				// This is done here for demonstration purposes
+				
+				// The model matrix for each instance is also set on compute
+				// you could modify this to, eg, do frustrum culling on the GPU		
+				float ud[4] = { float(m_nDrawElements), float(m_sideSize), float(time), 0 };
+				bgfx::setUniform(u_drawParams, ud);
+						
+				bgfx::setBuffer(0, m_object_list_buffer, bgfx::Access::Read);
+				bgfx::setBuffer(1, m_indirect_buffer_handle, bgfx::Access::Write);
+				bgfx::setBuffer(2, m_instance_buffer, bgfx::Access::Write);
+				
+				bgfx::dispatch(0, m_indirect_program);
+				
+				// Submit our 1 draw call
+				// Set vertex and index buffer.
+				bgfx::setIndexBuffer(m_ibh);
+				bgfx::setVertexBuffer(0, m_vbh);
+				bgfx::setInstanceDataBuffer(m_instance_buffer, 0, m_sideSize*m_sideSize);
+				
+				// Set render states.
+				bgfx::setState(BGFX_STATE_DEFAULT);
+
+				// Submit primitive for rendering to view 0.
+				// note that this submission requires the draw count
+				bgfx::submit(0, m_program, m_indirect_buffer_handle, 0, m_sideSize*m_sideSize);
+			}
+			else
+			{
+				// Compute/Instancing is not supported
+				bool blink = uint32_t(time*3.0f)&1;
+				bgfx::dbgTextPrintf(0, 0, blink ? 0x4f : 0x04, " Compute/Instancing is not supported by GPU. ");
+			}
+			
+			// Advance to next frame. Rendering thread will be kicked to
+			// process submitted rendering primitives.
+			bgfx::frame();
+
+			return true;
+		}
+
+		return false;
+	}
+
+	entry::MouseState m_mouseState;
+
+	uint32_t m_width;
+	uint32_t m_height;
+	uint32_t m_debug;
+	uint32_t m_reset;
+	uint32_t m_sideSize;
+	uint32_t m_nDrawElements;
+
+	bgfx::VertexBufferHandle m_vbh;
+	bgfx::IndexBufferHandle  m_ibh;
+	bgfx::ProgramHandle m_program;
+	bgfx::IndirectBufferHandle  m_indirect_buffer_handle;
+	bgfx::ProgramHandle m_indirect_program;
+	bgfx::VertexBufferHandle m_object_list_buffer;
+	bgfx::DynamicVertexBufferHandle m_instance_buffer;
+	bgfx::UniformHandle u_drawParams;
+
+	int64_t m_timeOffset;
+};
+
+} // namespace
+
+ENTRY_IMPLEMENT_MAIN(
+	  DrawIndirect
+	, "48-drawindirect"
+	, "Simple example of indirect rendering to render multiple different meshes with 1 draw call"
+	, "https://bkaradzic.github.io/bgfx/examples.html#drawindirect"
+	);

+ 10 - 0
examples/48-drawindirect/makefile

@@ -0,0 +1,10 @@
+#
+# Copyright 2011-2022 Branimir Karadzic. All rights reserved.
+# License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
+#
+
+BGFX_DIR=../..
+RUNTIME_DIR=$(BGFX_DIR)/examples/runtime
+BUILD_DIR=../../.build
+
+include $(BGFX_DIR)/scripts/shader.mk

BIN
examples/48-drawindirect/screenshot.png


+ 2 - 0
examples/makefile

@@ -51,6 +51,7 @@ all:
 	@make -s --no-print-directory build -C 45-bokeh
 	@make -s --no-print-directory build -C 46-fsr
 	@make -s --no-print-directory build -C 47-pixelformats
+	@make -s --no-print-directory build -C 48-drawindirect
 
 rebuild:
 	@make -s --no-print-directory rebuild -C 01-cubes
@@ -100,6 +101,7 @@ rebuild:
 	@make -s --no-print-directory rebuild -C 45-bokeh
 	@make -s --no-print-directory rebuild -C 46-fsr
 	@make -s --no-print-directory rebuild -C 47-pixelformats
+	@make -s --no-print-directory rebuild -C 48-drawindirect
 
 rebuild-embedded:
 	@make -s --no-print-directory rebuild -C 02-metaballs

BIN
examples/runtime/shaders/dx11/cs_drawindirect.bin


BIN
examples/runtime/shaders/essl/cs_drawindirect.bin


BIN
examples/runtime/shaders/glsl/cs_drawindirect.bin


BIN
examples/runtime/shaders/metal/cs_drawindirect.bin


BIN
examples/runtime/shaders/spirv/cs_drawindirect.bin


+ 1 - 0
scripts/genie.lua

@@ -591,6 +591,7 @@ or _OPTIONS["with-combined-examples"] then
 		, "45-bokeh"
 		, "46-fsr"
 		, "47-pixelformats"
+		, "48-drawindirect"
 		)
 
 	-- 17-drawstress requires multithreading, does not compile for singlethreaded wasm

+ 1 - 1
scripts/shader.mk

@@ -127,7 +127,7 @@ VS_BIN = $(addprefix $(BUILD_INTERMEDIATE_DIR)/, $(addsuffix .bin, $(basename $(
 FS_BIN = $(addprefix $(BUILD_INTERMEDIATE_DIR)/, $(addsuffix .bin, $(basename $(notdir $(FS_SOURCES)))))
 CS_BIN = $(addprefix $(BUILD_INTERMEDIATE_DIR)/, $(addsuffix .bin, $(basename $(notdir $(CS_SOURCES)))))
 
-BIN = $(VS_BIN) $(FS_BIN)
+BIN = $(VS_BIN) $(FS_BIN) $(CS_BIN)
 ASM = $(VS_ASM) $(FS_ASM)
 
 ifeq ($(TARGET), $(filter $(TARGET),1 3 4 5 6 7))