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