Files
bgfx/examples/54-s2h/s2h.cpp
Branimir Karadžić 78ee247bf5 S2H cleanup. (#3964)
2026-09-05 19:46:00 +00:00

510 lines
15 KiB
C++

/*
* Copyright 2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
*/
#include "common.h"
#include "bgfx_utils.h"
#include "imgui/imgui.h"
namespace
{
struct PosColorTexCoord0Vertex
{
float m_x;
float m_y;
float m_z;
uint32_t m_abgr;
float m_u;
float m_v;
static void init()
{
ms_layout
.begin()
.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
.add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true)
.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
.end();
}
static bgfx::VertexLayout ms_layout;
};
bgfx::VertexLayout PosColorTexCoord0Vertex::ms_layout;
static const char* s_exampleNames[] =
{
"Features: 3D",
"Features: 2D",
"Features: Gather",
"Features: Debug Zoom",
"Features: Scatter",
"Features: Quad VS/PS",
"Features: Table",
"Features: Quad Post",
"Features: 2D Arrow",
"Features: Generate User Font",
"Features: Use User Font",
"Features: 2D Coordinate System",
"Hello World",
"Hello Screen",
"Features: Clear",
"Zoom 2D",
};
void renderScreenSpaceQuad(uint8_t _view, bgfx::ProgramHandle _program, bool _originBottomLeft = false)
{
bgfx::TransientVertexBuffer tvb;
bgfx::TransientIndexBuffer tib;
if (bgfx::allocTransientBuffers(&tvb, PosColorTexCoord0Vertex::ms_layout, 4, &tib, 6) )
{
PosColorTexCoord0Vertex* vertex = (PosColorTexCoord0Vertex*)tvb.data;
const float minV = _originBottomLeft ? 1.0f : 0.0f;
const float maxV = _originBottomLeft ? 0.0f : 1.0f;
vertex[0] = { -1.0f, -1.0f, 0.0f, 0xffffffff, 0.0f, maxV };
vertex[1] = { 1.0f, -1.0f, 0.0f, 0xffffffff, 1.0f, maxV };
vertex[2] = { 1.0f, 1.0f, 0.0f, 0xffffffff, 1.0f, minV };
vertex[3] = { -1.0f, 1.0f, 0.0f, 0xffffffff, 0.0f, minV };
uint16_t* indices = (uint16_t*)tib.data;
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 0;
indices[4] = 2;
indices[5] = 3;
bgfx::setState(BGFX_STATE_WRITE_RGB|BGFX_STATE_WRITE_A);
bgfx::setIndexBuffer(&tib);
bgfx::setVertexBuffer(0, &tvb);
bgfx::submit(_view, _program);
}
}
class ExampleS2h : public entry::AppI
{
public:
ExampleS2h(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_NONE;
m_reset = BGFX_RESET_VSYNC;
bgfx::Init init;
init.type = args.m_type;
init.vendorId = args.m_pciId;
init.platformData.type = entry::getNativeWindowHandleType();
init.swapChain.nwh = entry::getNativeWindowHandle(entry::kDefaultWindowHandle);
init.swapChain.ndt = entry::getNativeDisplayHandle();
init.swapChain.width = m_width;
init.swapChain.height = m_height;
init.reset = m_reset;
bgfx::init(init);
bgfx::setDebug(m_debug);
bgfx::setViewClear(0, BGFX_CLEAR_COLOR|BGFX_CLEAR_DEPTH, 0x050505ff, 1.0f, 0);
PosColorTexCoord0Vertex::init();
for (bgfx::ProgramHandle& program : m_program)
{
program = BGFX_INVALID_HANDLE;
}
m_timeUniform = bgfx::createUniform("u_s2hTime", bgfx::UniformFreq::Frame, bgfx::UniformType::Vec4);
m_uiStateUniform = bgfx::createUniform("u_s2hUiState", bgfx::UniformFreq::Frame, bgfx::UniformType::Vec4);
m_colorUniform = bgfx::createUniform("u_s2hColor", bgfx::UniformFreq::Frame, bgfx::UniformType::Vec4);
m_mouseUniform = bgfx::createUniform("u_s2hMouse", bgfx::UniformFreq::Frame, bgfx::UniformType::Vec4);
m_zoomUniform = bgfx::createUniform("u_s2hZoom", bgfx::UniformFreq::Frame, bgfx::UniformType::Vec4);
m_scatterSizeUniform = bgfx::createUniform("u_s2hScatterSize", bgfx::UniformFreq::Frame, bgfx::UniformType::Vec4);
m_quadPostSampler = bgfx::createUniform("s_quadPostColor", bgfx::UniformType::Sampler);
m_scatterSampler = bgfx::createUniform("s_scatterColor", bgfx::UniformType::Sampler);
m_program[ 0] = loadProgram("vs_s2h", "fs_s2h_3d");
m_program[ 1] = loadProgram("vs_s2h", "fs_s2h_2d");
m_program[ 2] = loadProgram("vs_s2h", "fs_s2h_gather");
m_program[ 5] = loadProgram("vs_s2h", "fs_s2h_quadvsps");
m_program[ 6] = loadProgram("vs_s2h", "fs_s2h_table");
m_program[ 7] = loadProgram("vs_s2h", "fs_s2h_quadpost_scene");
m_program[ 8] = loadProgram("vs_s2h", "fs_s2h_arrow");
m_program[ 9] = loadProgram("vs_s2h", "fs_s2h_generate_user_font");
m_program[11] = loadProgram("vs_s2h", "fs_s2h_coordinate_system");
m_program[12] = loadProgram("vs_s2h", "fs_s2h");
m_program[13] = loadProgram("vs_s2h", "fs_s2h_screen");
m_program[14] = loadProgram("vs_s2h", "fs_s2h_clear");
m_program[15] = loadProgram("vs_s2h", "fs_s2h_zoom2d");
m_quadPostProgram = loadProgram("vs_s2h", "fs_s2h_quadpost");
m_userFontProgram = loadProgram("vs_s2h", "fs_s2h_use_user_font");
m_debugZoomProgram = loadProgram("vs_s2h", "fs_s2h_debug_zoom");
m_scatterClearProgram = loadProgram("cs_s2h_scatter_clear", NULL);
m_scatterProgram = loadProgram("cs_s2h_scatter", NULL);
m_scatterDisplayProgram = loadProgram("vs_s2h", "fs_s2h_scatter_display");
createQuadPostTarget();
createScatterTarget();
imguiCreate();
m_frameTime.reset();
}
int shutdown() override
{
imguiDestroy();
for (bgfx::ProgramHandle& program : m_program)
{
if (bgfx::isValid(program) )
{
bgfx::destroy(program);
}
}
bgfx::destroy(m_quadPostProgram);
bgfx::destroy(m_userFontProgram);
bgfx::destroy(m_debugZoomProgram);
bgfx::destroy(m_scatterClearProgram);
bgfx::destroy(m_scatterProgram);
bgfx::destroy(m_scatterDisplayProgram);
bgfx::destroy(m_quadPostFrameBuffer);
bgfx::destroy(m_quadPostTexture);
bgfx::destroy(m_quadPostSampler);
bgfx::destroy(m_scatterTexture);
bgfx::destroy(m_scatterSampler);
bgfx::destroy(m_scatterSizeUniform);
bgfx::destroy(m_timeUniform);
bgfx::destroy(m_uiStateUniform);
bgfx::destroy(m_colorUniform);
bgfx::destroy(m_mouseUniform);
bgfx::destroy(m_zoomUniform);
bgfx::shutdown();
return 0;
}
bool update() override
{
if (!entry::processEvents(m_width, m_height, m_debug, m_reset, &m_mouseState) )
{
m_frameTime.frame();
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) );
m_gatherMouseDown = m_mouseState.m_buttons[entry::MouseButton::Left];
showExampleDialog(this);
drawSettings();
if (2 == m_example)
{
updateGatherControls();
}
else if (15 == m_example)
{
updateZoomControls();
}
imguiEndFrame();
const float time[] =
{
bx::toSeconds<float>(m_frameTime.getDurationTime() ),
0.0f,
0.0f,
0.0f,
};
const float uiState[] =
{
float(m_gatherRadio),
m_gatherCheckbox ? 1.0f : 0.0f,
m_gatherColor[3],
0.0f,
};
const float mouse[] =
{
float(m_mouseState.m_mx),
float(m_mouseState.m_my),
m_gatherMouseDown ? 1.0f : 0.0f,
0.0f,
};
const float zoom[] =
{
m_zoomPan[0],
m_zoomPan[1],
m_zoomScale,
0.0f,
};
const bool originBottomLeft = bgfx::getCaps()->originBottomLeft;
const float scatterSize[] = { float(m_width), float(m_height), originBottomLeft ? 1.0f : 0.0f, 0.0f };
bgfx::setFrameUniform(m_timeUniform, time);
bgfx::setFrameUniform(m_uiStateUniform, uiState);
bgfx::setFrameUniform(m_colorUniform, m_gatherColor);
bgfx::setFrameUniform(m_mouseUniform, mouse);
bgfx::setFrameUniform(m_zoomUniform, zoom);
bgfx::setFrameUniform(m_scatterSizeUniform, scatterSize);
if (4 == m_example)
{
const uint32_t dispatchWidth = (m_width + 7) / 8;
const uint32_t dispatchHeight = (m_height + 7) / 8;
bgfx::setImage(0, m_scatterTexture, 0, bgfx::Access::Write, bgfx::TextureFormat::RGBA8);
bgfx::dispatch(2, m_scatterClearProgram, uint16_t(dispatchWidth), uint16_t(dispatchHeight) );
bgfx::setImage(0, m_scatterTexture, 0, bgfx::Access::Write, bgfx::TextureFormat::RGBA8);
bgfx::dispatch(3, m_scatterProgram, 1, 1);
bgfx::setViewFrameBuffer(4, BGFX_INVALID_HANDLE);
bgfx::setViewRect(4, 0, 0, uint16_t(m_width), uint16_t(m_height) );
bgfx::setTexture(0, m_scatterSampler, m_scatterTexture);
renderScreenSpaceQuad(4, m_scatterDisplayProgram, originBottomLeft);
}
else if (3 == m_example || 7 == m_example || 10 == m_example)
{
bgfx::setViewFrameBuffer(0, m_quadPostFrameBuffer);
bgfx::setViewRect(0, 0, 0, uint16_t(m_width), uint16_t(m_height) );
bgfx::setViewClear(0, BGFX_CLEAR_COLOR, 0x101018ff);
renderScreenSpaceQuad(0, 10 == m_example ? m_program[9] : m_program[7]);
bgfx::setViewFrameBuffer(1, BGFX_INVALID_HANDLE);
bgfx::setViewRect(1, 0, 0, uint16_t(m_width), uint16_t(m_height) );
bgfx::setTexture(0, m_quadPostSampler, m_quadPostTexture);
renderScreenSpaceQuad(1, 7 == m_example ? m_quadPostProgram : (10 == m_example ? m_userFontProgram : m_debugZoomProgram), originBottomLeft);
}
else
{
bgfx::setViewFrameBuffer(0, BGFX_INVALID_HANDLE);
bgfx::setViewRect(0, 0, 0, uint16_t(m_width), uint16_t(m_height) );
bgfx::touch(0);
renderScreenSpaceQuad(0, m_program[m_example]);
}
bgfx::frame();
return true;
}
return false;
}
void drawSettings()
{
ImGui::SetNextWindowPos(
ImVec2(m_width - m_width / 4.0f - 10.0f, 10.0f)
, ImGuiCond_FirstUseEver
);
ImGui::SetNextWindowSize(
ImVec2(m_width / 4.0f, m_height / 1.35f)
, ImGuiCond_FirstUseEver
);
ImGui::Begin("Settings", NULL, 0);
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.55f);
ImGui::Combo("example", &m_example, s_exampleNames, BX_COUNTOF(s_exampleNames) );
ImGui::Separator();
if (2 == m_example)
{
ImGui::Text("Gather controls:");
ImGui::RadioButton("red##gather-radio-red", &m_gatherRadio, 1); ImGui::SameLine();
ImGui::RadioButton("green##gather-radio-green", &m_gatherRadio, 2); ImGui::SameLine();
ImGui::RadioButton("blue##gather-radio-blue", &m_gatherRadio, 3);
ImGui::Checkbox("checkbox##gather-checkbox", &m_gatherCheckbox);
ImGui::SliderFloat("alpha##gather-alpha", &m_gatherColor[3], 0.0f, 1.0f);
ImGui::SliderFloat("red##gather-red", &m_gatherColor[0], 0.0f, 1.0f);
ImGui::SliderFloat("green##gather-green", &m_gatherColor[1], 0.0f, 1.0f);
ImGui::SliderFloat("blue##gather-blue", &m_gatherColor[2], 0.0f, 1.0f);
}
else if (15 == m_example)
{
ImGui::TextWrapped("Left-drag to pan the zoomed canvas. Right-drag vertically to zoom. The fragment shader draws the grid and S2H overlay at the transformed pixel coordinates.");
if (ImGui::Button("Reset view##zoom2d-reset") )
{
resetZoom();
}
}
else
{
ImGui::TextWrapped("Select a Shader To Human sample. Sample-specific controls appear here.");
}
ImGui::PopItemWidth();
ImGui::End();
}
void createQuadPostTarget()
{
m_quadPostTexture = bgfx::createTexture2D(uint16_t(m_width), uint16_t(m_height), false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT);
m_quadPostFrameBuffer = bgfx::createFrameBuffer(1, &m_quadPostTexture, false);
}
void createScatterTarget()
{
m_scatterTexture = bgfx::createTexture2D(
uint16_t(m_width)
, uint16_t(m_height)
, false
, 1
, bgfx::TextureFormat::RGBA8
, BGFX_TEXTURE_COMPUTE_WRITE|BGFX_SAMPLER_U_CLAMP|BGFX_SAMPLER_V_CLAMP
);
}
void updateGatherControls()
{
const ImVec2 mouse = ImGui::GetIO().MousePos;
const auto isInside = [&mouse](float _x, float _y, float _width, float _height)
{
return mouse.x >= _x && mouse.x < _x + _width
&& mouse.y >= _y && mouse.y < _y + _height;
};
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) )
{
if (isInside(105.0f, 218.0f, 16.0f, 16.0f) )
{
m_gatherRadio = 1;
}
else if (isInside(121.0f, 218.0f, 16.0f, 16.0f) )
{
m_gatherRadio = 2;
}
else if (isInside(137.0f, 218.0f, 16.0f, 16.0f) )
{
m_gatherRadio = 3;
}
else if (isInside(105.0f, 250.0f, 80.0f, 16.0f) )
{
m_gatherRadio = 0;
}
else if (isInside(105.0f, 282.0f, 16.0f, 16.0f) )
{
m_gatherCheckbox = !m_gatherCheckbox;
}
}
if (ImGui::IsMouseDown(ImGuiMouseButton_Left) )
{
auto updateSlider = [&isInside, &mouse](float _x, float _y, float _width, float& _value)
{
if (isInside(_x, _y, _width, 14.0f) )
{
_value = bx::clamp((mouse.x - (_x + 2.0f)) / (_width - 4.0f), 0.0f, 1.0f);
}
};
updateSlider(42.0f, 346.0f, 128.0f, m_gatherColor[3]);
updateSlider(90.0f, 378.0f, 80.0f, m_gatherColor[0]);
updateSlider(90.0f, 394.0f, 80.0f, m_gatherColor[1]);
updateSlider(90.0f, 410.0f, 80.0f, m_gatherColor[2]);
}
}
void resetZoom()
{
m_zoomPan[0] = 0.0f;
m_zoomPan[1] = 0.0f;
m_zoomScale = 1.0f;
}
void updateZoomControls()
{
const int32_t mouseX = m_mouseState.m_mx;
const int32_t mouseY = m_mouseState.m_my;
const bool leftDown = m_mouseState.m_buttons[entry::MouseButton::Left];
const bool rightDown = m_mouseState.m_buttons[entry::MouseButton::Right];
if (ImGui::GetIO().WantCaptureMouse)
{
m_zoomLastMouseX = mouseX;
m_zoomLastMouseY = mouseY;
m_zoomLeftDown = false;
m_zoomRightDown = false;
return;
}
if (leftDown && m_zoomLeftDown)
{
m_zoomPan[0] -= float(mouseX - m_zoomLastMouseX) / m_zoomScale;
m_zoomPan[1] += float(mouseY - m_zoomLastMouseY) / m_zoomScale;
}
if (rightDown && m_zoomRightDown)
{
m_zoomScale = bx::clamp(m_zoomScale * bx::pow(2.0f, float(m_zoomLastMouseY - mouseY) * 0.02f), 0.125f, 32.0f);
}
m_zoomLastMouseX = mouseX;
m_zoomLastMouseY = mouseY;
m_zoomLeftDown = leftDown;
m_zoomRightDown = rightDown;
}
entry::MouseState m_mouseState;
uint32_t m_width;
uint32_t m_height;
uint32_t m_debug;
uint32_t m_reset;
int m_example = 0;
bgfx::ProgramHandle m_program[BX_COUNTOF(s_exampleNames)];
bgfx::ProgramHandle m_quadPostProgram;
bgfx::ProgramHandle m_userFontProgram;
bgfx::ProgramHandle m_debugZoomProgram;
bgfx::ProgramHandle m_scatterClearProgram;
bgfx::ProgramHandle m_scatterProgram;
bgfx::ProgramHandle m_scatterDisplayProgram;
bgfx::TextureHandle m_quadPostTexture;
bgfx::FrameBufferHandle m_quadPostFrameBuffer;
bgfx::TextureHandle m_scatterTexture;
bgfx::UniformHandle m_quadPostSampler;
bgfx::UniformHandle m_scatterSampler;
bgfx::UniformHandle m_scatterSizeUniform;
bgfx::UniformHandle m_timeUniform;
bgfx::UniformHandle m_uiStateUniform;
bgfx::UniformHandle m_colorUniform;
bgfx::UniformHandle m_mouseUniform;
bgfx::UniformHandle m_zoomUniform;
FrameTime m_frameTime;
int m_gatherRadio = 0;
bool m_gatherCheckbox = false;
bool m_gatherMouseDown = false;
float m_gatherColor[4] = { 0.0f, 0.3f, 1.0f, 1.0f };
float m_zoomPan[2] = { 0.0f, 0.0f };
float m_zoomScale = 1.0f;
int32_t m_zoomLastMouseX = 0;
int32_t m_zoomLastMouseY = 0;
bool m_zoomLeftDown = false;
bool m_zoomRightDown = false;
};
} // namespace
ENTRY_IMPLEMENT_MAIN(
ExampleS2h
, "54-s2h"
, "Shader To Human: Hello World."
, ""
);