1 /*
2  * Copyright 2016 Joseph Cherlin. All rights reserved.
3  * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
4  */
5 
6 #include "common.h"
7 #include "bgfx_utils.h"
8 #include "imgui/imgui.h"
9 #include <bx/rng.h>
10 #include <map>
11 
12 namespace
13 {
14 
15 #define RENDER_PASS_SHADING 0  // Default forward rendered geo with simple shading
16 #define RENDER_PASS_ID      1  // ID buffer for picking
17 #define RENDER_PASS_BLIT    2  // Blit GPU render target to CPU texture
18 
19 #define ID_DIM 8  // Size of the ID buffer
20 
21 class ExamplePicking : public entry::AppI
22 {
23 public:
ExamplePicking(const char * _name,const char * _description,const char * _url)24 	ExamplePicking(const char* _name, const char* _description, const char* _url)
25 		: entry::AppI(_name, _description, _url)
26 	{
27 	}
28 
init(int32_t _argc,const char * const * _argv,uint32_t _width,uint32_t _height)29 	void init(int32_t _argc, const char* const* _argv, uint32_t _width, uint32_t _height) override
30 	{
31 		Args args(_argc, _argv);
32 
33 		m_width  = _width;
34 		m_height = _height;
35 		m_debug  = BGFX_DEBUG_NONE;
36 		m_reset  = BGFX_RESET_VSYNC;
37 
38 		bgfx::Init init;
39 		init.type     = args.m_type;
40 		init.vendorId = args.m_pciId;
41 		init.resolution.width  = m_width;
42 		init.resolution.height = m_height;
43 		init.resolution.reset  = m_reset;
44 		bgfx::init(init);
45 
46 		// Enable debug text.
47 		bgfx::setDebug(m_debug);
48 
49 		// Set up screen clears
50 		bgfx::setViewClear(RENDER_PASS_SHADING
51 			, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH
52 			, 0x303030ff
53 			, 1.0f
54 			, 0
55 			);
56 
57 		// ID buffer clears to black, which represnts clicking on nothing (background)
58 		bgfx::setViewClear(RENDER_PASS_ID
59 			, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH
60 			, 0x000000ff
61 			, 1.0f
62 			, 0
63 			);
64 
65 		// Create uniforms
66 		u_tint = bgfx::createUniform("u_tint", bgfx::UniformType::Vec4); // Tint for when you click on items
67 		u_id   = bgfx::createUniform("u_id",   bgfx::UniformType::Vec4); // ID for drawing into ID buffer
68 
69 		// Create program from shaders.
70 		m_shadingProgram = loadProgram("vs_picking_shaded", "fs_picking_shaded"); // Blinn shading
71 		m_idProgram      = loadProgram("vs_picking_shaded", "fs_picking_id");     // Shader for drawing into ID buffer
72 
73 		static const char* meshPaths[] =
74 		{
75 			"meshes/orb.bin",
76 			"meshes/column.bin",
77 			"meshes/bunny.bin",
78 			"meshes/cube.bin",
79 			"meshes/tree.bin",
80 			"meshes/hollowcube.bin",
81 		};
82 
83 		static const float meshScale[] =
84 		{
85 			0.5f,
86 			0.05f,
87 			0.5f,
88 			0.25f,
89 			0.05f,
90 			0.05f,
91 		};
92 
93 		m_highlighted = UINT32_MAX;
94 		m_reading = 0;
95 		m_currFrame = UINT32_MAX;
96 		m_fov = 3.0f;
97 		m_cameraSpin = false;
98 
99 		bx::RngMwc mwc;  // Random number generator
100 		for (uint32_t ii = 0; ii < 12; ++ii)
101 		{
102 			m_meshes[ii]    = meshLoad(meshPaths[ii % BX_COUNTOF(meshPaths)]);
103 			m_meshScale[ii] = meshScale[ii % BX_COUNTOF(meshPaths)];
104 			// For the sake of this example, we'll give each mesh a random color,  so the debug output looks colorful.
105 			// In an actual app, you'd probably just want to count starting from 1
106 			uint32_t rr = mwc.gen() % 256;
107 			uint32_t gg = mwc.gen() % 256;
108 			uint32_t bb = mwc.gen() % 256;
109 			m_idsF[ii][0] = rr / 255.0f;
110 			m_idsF[ii][1] = gg / 255.0f;
111 			m_idsF[ii][2] = bb / 255.0f;
112 			m_idsF[ii][3] = 1.0f;
113 			m_idsU[ii] = rr + (gg << 8) + (bb << 16) + (255u << 24);
114 		}
115 
116 		m_timeOffset = bx::getHPCounter();
117 
118 		// Set up ID buffer, which has a color target and depth buffer
119 		m_pickingRT = bgfx::createTexture2D(ID_DIM, ID_DIM, false, 1, bgfx::TextureFormat::RGBA8, 0
120 			| BGFX_TEXTURE_RT
121 			| BGFX_SAMPLER_MIN_POINT
122 			| BGFX_SAMPLER_MAG_POINT
123 			| BGFX_SAMPLER_MIP_POINT
124 			| BGFX_SAMPLER_U_CLAMP
125 			| BGFX_SAMPLER_V_CLAMP
126 			);
127 		m_pickingRTDepth = bgfx::createTexture2D(ID_DIM, ID_DIM, false, 1, bgfx::TextureFormat::D24S8, 0
128 			| BGFX_TEXTURE_RT
129 			| BGFX_SAMPLER_MIN_POINT
130 			| BGFX_SAMPLER_MAG_POINT
131 			| BGFX_SAMPLER_MIP_POINT
132 			| BGFX_SAMPLER_U_CLAMP
133 			| BGFX_SAMPLER_V_CLAMP
134 			);
135 
136 		// CPU texture for blitting to and reading ID buffer so we can see what was clicked on.
137 		// Impossible to read directly from a render target, you *must* blit to a CPU texture
138 		// first. Algorithm Overview: Render on GPU -> Blit to CPU texture -> Read from CPU
139 		// texture.
140 		m_blitTex = bgfx::createTexture2D(ID_DIM, ID_DIM, false, 1, bgfx::TextureFormat::RGBA8, 0
141 			| BGFX_TEXTURE_BLIT_DST
142 			| BGFX_TEXTURE_READ_BACK
143 			| BGFX_SAMPLER_MIN_POINT
144 			| BGFX_SAMPLER_MAG_POINT
145 			| BGFX_SAMPLER_MIP_POINT
146 			| BGFX_SAMPLER_U_CLAMP
147 			| BGFX_SAMPLER_V_CLAMP
148 			);
149 
150 		bgfx::TextureHandle rt[2] =
151 		{
152 			m_pickingRT,
153 			m_pickingRTDepth
154 		};
155 		m_pickingFB = bgfx::createFrameBuffer(BX_COUNTOF(rt), rt, true);
156 
157 		imguiCreate();
158 	}
159 
shutdown()160 	int shutdown() override
161 	{
162 		for (uint32_t ii = 0; ii < 12; ++ii)
163 		{
164 			meshUnload(m_meshes[ii]);
165 		}
166 
167 		// Cleanup.
168 		bgfx::destroy(m_shadingProgram);
169 		bgfx::destroy(m_idProgram);
170 
171 		bgfx::destroy(u_tint);
172 		bgfx::destroy(u_id);
173 
174 		bgfx::destroy(m_pickingFB);
175 		bgfx::destroy(m_pickingRT);
176 		bgfx::destroy(m_pickingRTDepth);
177 		bgfx::destroy(m_blitTex);
178 
179 		imguiDestroy();
180 
181 		// Shutdown bgfx.
182 		bgfx::shutdown();
183 
184 		return 0;
185 	}
186 
update()187 	bool update() override
188 	{
189 		if (!entry::processEvents(m_width, m_height, m_debug, m_reset, &m_mouseState) )
190 		{
191 			// Draw UI
192 			imguiBeginFrame(
193 				   m_mouseState.m_mx
194 				,  m_mouseState.m_my
195 				, (m_mouseState.m_buttons[entry::MouseButton::Left] ? IMGUI_MBUT_LEFT : 0)
196 				| (m_mouseState.m_buttons[entry::MouseButton::Right] ? IMGUI_MBUT_RIGHT : 0)
197 				| (m_mouseState.m_buttons[entry::MouseButton::Middle] ? IMGUI_MBUT_MIDDLE : 0)
198 				,  m_mouseState.m_mz
199 				, uint16_t(m_width)
200 				, uint16_t(m_height)
201 				);
202 
203 			const bgfx::Caps* caps = bgfx::getCaps();
204 			bool blitSupport = 0 != (caps->supported & BGFX_CAPS_TEXTURE_BLIT);
205 
206 			showExampleDialog(this
207 				, !blitSupport
208 				? "BGFX_CAPS_TEXTURE_BLIT is not supported."
209 				: NULL
210 				);
211 
212 			if (blitSupport)
213 			{
214 				ImGui::SetNextWindowPos(
215 					  ImVec2(m_width - m_width / 5.0f - 10.0f, 10.0f)
216 					, ImGuiCond_FirstUseEver
217 					);
218 				ImGui::SetNextWindowSize(
219 					  ImVec2(m_width / 5.0f, m_height / 2.0f)
220 					, ImGuiCond_FirstUseEver
221 					);
222 				ImGui::Begin("Settings"
223 					, NULL
224 					, 0
225 					);
226 
227 				ImGui::Image(m_pickingRT, ImVec2(m_width / 5.0f - 16.0f, m_width / 5.0f - 16.0f) );
228 				ImGui::SliderFloat("Field of view", &m_fov, 1.0f, 60.0f);
229 				ImGui::Checkbox("Spin Camera", &m_cameraSpin);
230 
231 				ImGui::End();
232 
233 				bgfx::setViewFrameBuffer(RENDER_PASS_ID, m_pickingFB);
234 
235 				float time = (float)( (bx::getHPCounter() - m_timeOffset) / double(bx::getHPFrequency() ) );
236 
237 				// Set up matrices for basic forward renderer
238 				const float camSpeed = 0.25;
239 				float cameraSpin = (float)m_cameraSpin;
240 				float eyeDist = 2.5f;
241 
242 				const bx::Vec3 at = { 0.0f, 0.0f, 0.0f };
243 				const bx::Vec3 eye =
244 				{
245 					-eyeDist * bx::sin(time*cameraSpin*camSpeed),
246 					0.0f,
247 					-eyeDist * bx::cos(time*cameraSpin*camSpeed),
248 				};
249 
250 				float view[16];
251 				bx::mtxLookAt(view, eye, at);
252 
253 				float proj[16];
254 				bx::mtxProj(proj, 60.0f, float(m_width) / float(m_height), 0.1f, 100.0f, caps->homogeneousDepth);
255 
256 				// Set up view rect and transform for the shaded pass
257 				bgfx::setViewRect(RENDER_PASS_SHADING, 0, 0, uint16_t(m_width), uint16_t(m_height) );
258 				bgfx::setViewTransform(RENDER_PASS_SHADING, view, proj);
259 
260 				// Set up picking pass
261 				float viewProj[16];
262 				bx::mtxMul(viewProj, view, proj);
263 
264 				float invViewProj[16];
265 				bx::mtxInverse(invViewProj, viewProj);
266 
267 				// Mouse coord in NDC
268 				float mouseXNDC = ( m_mouseState.m_mx             / (float)m_width ) * 2.0f - 1.0f;
269 				float mouseYNDC = ((m_height - m_mouseState.m_my) / (float)m_height) * 2.0f - 1.0f;
270 
271 				const bx::Vec3 pickEye = bx::mulH({ mouseXNDC, mouseYNDC, 0.0f }, invViewProj);
272 				const bx::Vec3 pickAt  = bx::mulH({ mouseXNDC, mouseYNDC, 1.0f }, invViewProj);
273 
274 				// Look at our unprojected point
275 				float pickView[16];
276 				bx::mtxLookAt(pickView, pickEye, pickAt);
277 
278 				// Tight FOV is best for picking
279 				float pickProj[16];
280 				bx::mtxProj(pickProj, m_fov, 1, 0.1f, 100.0f, caps->homogeneousDepth);
281 
282 				// View rect and transforms for picking pass
283 				bgfx::setViewRect(RENDER_PASS_ID, 0, 0, ID_DIM, ID_DIM);
284 				bgfx::setViewTransform(RENDER_PASS_ID, pickView, pickProj);
285 
286 				// Now that our passes are set up, we can finally draw each mesh
287 
288 				// Picking highlights a mesh so we'll set up this tint color
289 				const float tintBasic[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
290 				const float tintHighlighted[4] = { 0.3f, 0.3f, 2.0f, 1.0f };
291 
292 				for (uint32_t mesh = 0; mesh < 12; ++mesh)
293 				{
294 					const float scale = m_meshScale[mesh];
295 
296 					// Set up transform matrix for each mesh
297 					float mtx[16];
298 					bx::mtxSRT(mtx
299 						, scale, scale, scale
300 						, 0.0f
301 						, time*0.37f*(mesh % 2 ? 1.0f : -1.0f)
302 						, 0.0f
303 						, (mesh % 4) - 1.5f
304 						, (mesh / 4) - 1.25f
305 						, 0.0f
306 						);
307 
308 					// Submit mesh to both of our render passes
309 					// Set uniform based on if this is the highlighted mesh
310 					bgfx::setUniform(u_tint
311 						, mesh == m_highlighted
312 						? tintHighlighted
313 						: tintBasic
314 						);
315 					meshSubmit(m_meshes[mesh], RENDER_PASS_SHADING, m_shadingProgram, mtx);
316 
317 					// Submit ID pass based on mesh ID
318 					bgfx::setUniform(u_id, m_idsF[mesh]);
319 					meshSubmit(m_meshes[mesh], RENDER_PASS_ID, m_idProgram, mtx);
320 				}
321 
322 				// If the user previously clicked, and we're done reading data from GPU, look at ID buffer on CPU
323 				// Whatever mesh has the most pixels in the ID buffer is the one the user clicked on.
324 				if (m_reading == m_currFrame)
325 				{
326 					m_reading = 0;
327 					std::map<uint32_t, uint32_t> ids;  // This contains all the IDs found in the buffer
328 					uint32_t maxAmount = 0;
329 					for (uint8_t *x = m_blitData; x < m_blitData + ID_DIM * ID_DIM * 4;)
330 					{
331 						uint8_t rr = *x++;
332 						uint8_t gg = *x++;
333 						uint8_t bb = *x++;
334 						uint8_t aa = *x++;
335 
336 						if (bgfx::RendererType::Direct3D9 == caps->rendererType)
337 						{
338 							// Comes back as BGRA
339 							uint8_t temp = rr;
340 							rr = bb;
341 							bb = temp;
342 						}
343 
344 						if (0 == (rr|gg|bb) ) // Skip background
345 						{
346 							continue;
347 						}
348 
349 						uint32_t hashKey = rr + (gg << 8) + (bb << 16) + (aa << 24);
350 						std::map<uint32_t, uint32_t>::iterator mapIter = ids.find(hashKey);
351 						uint32_t amount = 1;
352 						if (mapIter != ids.end() )
353 						{
354 							amount = mapIter->second + 1;
355 						}
356 
357 						ids[hashKey] = amount; // Amount of times this ID (color) has been clicked on in buffer
358 						maxAmount = maxAmount > amount
359 							? maxAmount
360 							: amount
361 							;
362 					}
363 
364 					uint32_t idKey = 0;
365 					m_highlighted = UINT32_MAX;
366 					if (maxAmount)
367 					{
368 						for (std::map<uint32_t, uint32_t>::iterator mapIter = ids.begin(); mapIter != ids.end(); mapIter++)
369 						{
370 							if (mapIter->second == maxAmount)
371 							{
372 								idKey = mapIter->first;
373 								break;
374 							}
375 						}
376 
377 						for (uint32_t ii = 0; ii < 12; ++ii)
378 						{
379 							if (m_idsU[ii] == idKey)
380 							{
381 								m_highlighted = ii;
382 								break;
383 							}
384 						}
385 					}
386 				}
387 
388 				// Start a new readback?
389 				if (!m_reading
390 				&&  m_mouseState.m_buttons[entry::MouseButton::Left])
391 				{
392 					// Blit and read
393 					bgfx::blit(RENDER_PASS_BLIT, m_blitTex, 0, 0, m_pickingRT);
394 					m_reading = bgfx::readTexture(m_blitTex, m_blitData);
395 				}
396 			}
397 
398 			imguiEndFrame();
399 
400 			// Advance to next frame. Rendering thread will be kicked to
401 			// process submitted rendering primitives.
402 			m_currFrame = bgfx::frame();
403 
404 			return true;
405 		}
406 
407 		return false;
408 	}
409 
410 	entry::MouseState m_mouseState;
411 
412 	uint32_t m_width;
413 	uint32_t m_height;
414 	uint32_t m_debug;
415 	uint32_t m_reset;
416 	int64_t m_timeOffset;
417 
418 	Mesh* m_meshes[12];
419 	float m_meshScale[12];
420 	float m_idsF[12][4];
421 	uint32_t m_idsU[12];
422 	uint32_t m_highlighted;
423 
424 	// Resource handles
425 	bgfx::ProgramHandle m_shadingProgram;
426 	bgfx::ProgramHandle m_idProgram;
427 	bgfx::UniformHandle u_tint;
428 	bgfx::UniformHandle u_id;
429 	bgfx::TextureHandle m_pickingRT;
430 	bgfx::TextureHandle m_pickingRTDepth;
431 	bgfx::TextureHandle m_blitTex;
432 	bgfx::FrameBufferHandle m_pickingFB;
433 
434 	uint8_t m_blitData[ID_DIM*ID_DIM * 4]; // Read blit into this
435 
436 	uint32_t m_reading;
437 	uint32_t m_currFrame;
438 
439 	float m_fov;
440 	bool  m_cameraSpin;
441 };
442 
443 } // namespace
444 
445 ENTRY_IMPLEMENT_MAIN(
446 	  ExamplePicking
447 	, "30-picking"
448 	, "Mouse picking via GPU texture readback."
449 	, "https://bkaradzic.github.io/bgfx/examples.html#picking"
450 	);
451