Skip to content

Commit e0695a5

Browse files
authored
Merge pull request #61 from Simple-Robotics/video-writer-performance
Rework texture downloader, screenshot capture and video recording, fix `CommandBuffer::submitAndAcquireFence()` not setting the internal pointer to null, fix velocity arrow direction in `RobotDebugScene`
2 parents e75e346 + 1bb9eee commit e0695a5

21 files changed

+567
-326
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- utils : add `FileDialogGui.h` (https://github.com/Simple-Robotics/candlewick/pull/61)
13+
14+
### Changed
15+
16+
- utils : avoid public inclusion of `libavutil/pixfmt` header for video recorder (https://github.com/Simple-Robotics/candlewick/pull/61)
17+
- core : `errors.h`: make terminate_with_message a template (with formatting arguments, etc) (https://github.com/Simple-Robotics/candlewick/pull/61)
18+
- utils: add VideoRecorder::close() API (to manually close recorder) (https://github.com/Simple-Robotics/candlewick/pull/61)
19+
- multibody/RobotDebug.cpp : fix velocity arrow direction (https://github.com/Simple-Robotics/candlewick/pull/61)
20+
21+
### Fixed
22+
23+
- core : fix `CommandBuffer::submitAndAcquireFence()` not setting the internal pointer to null
24+
1025
## [0.0.7] - 2025-05-17
1126

1227
### Added

examples/Ur5WithSystems.cpp

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "candlewick/multibody/RobotScene.h"
1515
#include "candlewick/multibody/RobotDebug.h"
1616
#include "candlewick/primitives/Primitives.h"
17+
#include "candlewick/utils/FileDialogGui.h"
1718
#include "candlewick/utils/WriteTextureToImage.h"
1819

1920
#include <imgui.h>
@@ -184,11 +185,17 @@ void eventLoop(const Renderer &renderer) {
184185
}
185186
}
186187

187-
Renderer createRenderer(Uint32 width, Uint32 height,
188-
SDL_GPUTextureFormat depth_stencil_format) {
189-
return Renderer{Device{auto_detect_shader_format_subset(), true},
190-
Window(__FILE__, int(width), int(height), 0),
191-
depth_stencil_format};
188+
static void screenshot_button_callback(Renderer &renderer,
189+
media::TransferBufferPool &pool,
190+
const char *filename) {
191+
const auto &device = renderer.device;
192+
CommandBuffer command_buffer{device};
193+
renderer.waitAndAcquireSwapchain(command_buffer);
194+
195+
SDL_Log("Saving screenshot at %s", filename);
196+
media::writeToFile(command_buffer, device, pool, renderer.swapchain,
197+
renderer.getSwapchainTextureFormat(), wWidth, wHeight,
198+
filename);
192199
}
193200

194201
int main(int argc, char **argv) {
@@ -206,8 +213,11 @@ int main(int argc, char **argv) {
206213
return 1;
207214

208215
// D16_UNORM works on macOS, D24_UNORM and D32_FLOAT break the depth prepass
209-
Renderer renderer =
210-
createRenderer(wWidth, wHeight, SDL_GPU_TEXTUREFORMAT_D16_UNORM);
216+
Renderer renderer{
217+
Device{auto_detect_shader_format_subset(), true},
218+
Window(__FILE__, wWidth, wHeight, 0),
219+
SDL_GPU_TEXTUREFORMAT_D16_UNORM,
220+
};
211221

212222
entt::registry registry{};
213223

@@ -313,6 +323,8 @@ int main(int argc, char **argv) {
313323

314324
FrustumBoundsDebugSystem frustumBoundsDebug{registry, renderer};
315325

326+
const char *screenshot_filename = nullptr;
327+
316328
GuiSystem gui_system{
317329
renderer, [&](const Renderer &r) {
318330
IMGUI_CHECKVERSION();
@@ -391,6 +403,13 @@ int main(int argc, char **argv) {
391403
ImGui::RadioButton("Heatmap", (int *)&depth_mode, 1);
392404
}
393405

406+
ImGui::SeparatorText("Screenshots");
407+
static GuiFileSaveDialog scr_dialog;
408+
scr_dialog.addFileDialog(renderer.window);
409+
if (ImGui::Button("Take screenshot")) {
410+
screenshot_filename = scr_dialog.filename.c_str();
411+
}
412+
394413
ImGui::SeparatorText("Robot model");
395414
ImGui::SetItemTooltip("Information about the displayed robot model.");
396415
multibody::guiAddPinocchioModelInfo(registry, model, geom_model);
@@ -414,44 +433,43 @@ int main(int argc, char **argv) {
414433

415434
Uint32 frameNo = 0;
416435

417-
srand(42);
436+
std::srand(42);
418437
Eigen::VectorXd q0 = pin::neutral(model);
419438
Eigen::VectorXd q1 = pin::randomConfiguration(model);
420439

421440
#ifdef CANDLEWICK_WITH_FFMPEG_SUPPORT
422441
media::VideoRecorder recorder{NoInit};
442+
media::TransferBufferPool transfer_buffer_pool{renderer.device};
423443
if (performRecording)
424-
recorder = media::VideoRecorder{wWidth, wHeight, "ur5.mp4"};
444+
recorder = media::VideoRecorder{wWidth,
445+
wHeight,
446+
"ur5.mp4",
447+
{
448+
.fps = 50,
449+
}};
425450
#endif
426451

427-
auto record_callback = [&] {
428-
#ifdef CANDLEWICK_WITH_FFMPEG_SUPPORT
429-
auto swapchain_format = renderer.getSwapchainTextureFormat();
430-
media::videoWriteTextureToFrame(renderer.device, recorder,
431-
renderer.swapchain, swapchain_format,
432-
wWidth, wHeight);
433-
#endif
434-
};
435-
436452
AABB &worldSpaceBounds = robot_scene.worldSpaceBounds;
437453
worldSpaceBounds.update({-1.f, -1.f, 0.f}, {+1.f, +1.f, 1.f});
438454

439455
frustumBoundsDebug.addBounds(worldSpaceBounds);
440456
frustumBoundsDebug.addFrustum(shadowPassInfo.cam);
441457

442458
Eigen::VectorXd q = q0;
459+
Eigen::VectorXd qn = q;
460+
Eigen::VectorXd v{model.nv};
443461
const double dt = 1e-2;
444462

445463
while (!quitRequested) {
446464
// logic
447465
eventLoop(renderer);
448466
double alpha = 0.5 * (1. + std::sin(frameNo * dt));
449-
Eigen::VectorXd qn = q;
450-
q = pin::interpolate(model, q0, q1, alpha);
451-
Eigen::VectorXd v = pin::difference(model, q, qn) / dt;
452-
pin::forwardKinematics(model, pin_data, q, v);
467+
pin::interpolate(model, q0, q1, alpha, qn);
468+
v = pin::difference(model, q, qn) / dt;
469+
pin::forwardKinematics(model, pin_data, qn, v);
453470
pin::updateFramePlacements(model, pin_data);
454471
pin::updateGeometryPlacements(model, pin_data, geom_model, geom_data);
472+
q = qn;
455473
debug_scene.update();
456474

457475
// acquire command buffer and swapchain
@@ -494,7 +512,18 @@ int main(int argc, char **argv) {
494512
command_buffer.submit();
495513

496514
if (performRecording) {
497-
record_callback();
515+
#ifdef CANDLEWICK_WITH_FFMPEG_SUPPORT
516+
CommandBuffer command_buffer = renderer.acquireCommandBuffer();
517+
auto swapchain_format = renderer.getSwapchainTextureFormat();
518+
media::videoWriteTextureToFrame(
519+
command_buffer, renderer.device, transfer_buffer_pool, recorder,
520+
renderer.swapchain, swapchain_format, wWidth, wHeight);
521+
#endif
522+
}
523+
if (screenshot_filename) {
524+
screenshot_button_callback(renderer, transfer_buffer_pool,
525+
screenshot_filename);
526+
screenshot_filename = nullptr;
498527
}
499528
frameNo++;
500529
}
@@ -507,6 +536,10 @@ int main(int argc, char **argv) {
507536
robot_scene.release();
508537
debug_scene.release();
509538
gui_system.release();
539+
#ifdef CANDLEWICK_WITH_FFMPEG_SUPPORT
540+
recorder.close();
541+
transfer_buffer_pool.release();
542+
#endif
510543
renderer.destroy();
511544
SDL_Quit();
512545
return 0;

examples/lib/Common.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#include "candlewick/core/MeshLayout.h"
2-
#include "candlewick/core/Shader.h"
31
#include "candlewick/core/math_types.h"
42
#include "candlewick/primitives/Cube.h"
53
#include "candlewick/utils/MeshTransforms.h"

src/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ add_library(
3939
candlewick/core/debug/Frustum.cpp
4040
candlewick/posteffects/ScreenSpaceShadows.cpp
4141
candlewick/posteffects/SSAO.cpp
42+
candlewick/utils/FileDialogGui.cpp
4243
candlewick/utils/LoadMesh.cpp
4344
candlewick/utils/LoadMaterial.cpp
4445
candlewick/utils/MeshData.cpp
@@ -56,7 +57,7 @@ add_library(
5657
candlewick/primitives/Internal.cpp
5758
candlewick/primitives/Plane.cpp
5859
candlewick/primitives/Sphere.cpp
59-
candlewick/third-party/sbti_lib.cpp
60+
candlewick/third-party/stbi_lib.cpp
6061
# imgui sources
6162
${IMGUI_SRC}
6263
)

src/candlewick/core/CommandBuffer.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,15 @@ CommandBuffer &CommandBuffer::operator=(CommandBuffer &&other) noexcept {
2121
return *this;
2222
}
2323

24+
bool CommandBuffer::cancel() noexcept {
25+
bool ret = SDL_CancelGPUCommandBuffer(_cmdBuf);
26+
_cmdBuf = nullptr;
27+
if (!ret) {
28+
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
29+
"Failed to cancel command buffer: %s", SDL_GetError());
30+
return false;
31+
}
32+
return true;
33+
}
34+
2435
} // namespace candlewick

src/candlewick/core/CommandBuffer.h

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,28 @@ class CommandBuffer {
4646
return true;
4747
}
4848

49-
bool cancel() noexcept {
50-
if (!(active() && SDL_CancelGPUCommandBuffer(_cmdBuf)))
51-
return false;
49+
SDL_GPUFence *submitAndAcquireFence() noexcept {
50+
SDL_GPUFence *fence = SDL_SubmitGPUCommandBufferAndAcquireFence(_cmdBuf);
5251
_cmdBuf = nullptr;
53-
return true;
52+
return fence;
5453
}
5554

55+
/// \brief Cancel the command buffer, returning the bool value from the
56+
/// wrapped SDL API.
57+
bool cancel() noexcept;
58+
59+
/// \brief Check if the command buffer is still active.
60+
///
61+
/// For this wrapper class, it means the internal pointer is non-null.
5662
bool active() const noexcept { return _cmdBuf; }
5763

5864
~CommandBuffer() noexcept {
5965
if (active()) {
6066
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
6167
"CommandBuffer object is being destroyed while still active! "
6268
"It will be cancelled.");
63-
SDL_CancelGPUCommandBuffer(_cmdBuf);
64-
_cmdBuf = nullptr;
65-
assert(false);
69+
[[maybe_unused]] bool ret = cancel();
70+
assert(ret);
6671
}
6772
}
6873

@@ -99,10 +104,6 @@ class CommandBuffer {
99104
SDL_PushGPUFragmentUniformData(_cmdBuf, slot_index, data, length);
100105
return *this;
101106
}
102-
103-
SDL_GPUFence *submitAndAcquireFence() {
104-
return SDL_SubmitGPUCommandBufferAndAcquireFence(_cmdBuf);
105-
}
106107
};
107108

108109
} // namespace candlewick

src/candlewick/core/DepthAndShadowPass.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ DepthPass::DepthPass(const Device &device, const MeshLayout &layout,
3737
};
3838
pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipeline_desc);
3939
if (!pipeline)
40-
terminate_with_message(
41-
std::format("Failed to create graphics pipeline: %s.", SDL_GetError()));
40+
terminate_with_message("Failed to create graphics pipeline: %s.",
41+
SDL_GetError());
4242
}
4343

4444
DepthPass::DepthPass(const Device &device, const MeshLayout &layout,

src/candlewick/core/errors.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,18 @@ namespace detail {
4747

4848
} // namespace detail
4949

50+
template <typename... Ts>
5051
[[noreturn]]
51-
inline void terminate_with_message(
52-
std::string_view msg,
53-
std::source_location location = std::source_location::current()) {
52+
void terminate_with_message(std::source_location location, std::string_view fmt,
53+
const Ts &...args) {
5454
throw std::runtime_error(
55-
detail::error_message_format(location.function_name(), "{:s}", msg)
56-
.c_str());
55+
detail::error_message_format(location.function_name(), fmt, args...));
56+
}
57+
58+
template <typename... Ts>
59+
[[noreturn]]
60+
void terminate_with_message(std::string_view fmt, const Ts &...args) {
61+
terminate_with_message(std::source_location::current(), fmt, args...);
5762
}
5863

5964
[[noreturn]]

src/candlewick/multibody/RobotDebug.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ void RobotDebugSystem::updateFrameVelocities(entt::registry &reg) {
6161
// the arrow mesh is posed z-up by default.
6262
// we need to rotate towards where the velocity is pointing,
6363
// then transform to the frame space.
64-
quatf.setFromTwoVectors(Float3::UnitZ(), -v);
64+
quatf.setFromTwoVectors(Float3::UnitZ(), v);
6565
Mat3f R2 = quatf.toRotationMatrix() * scaleMatrix;
6666
auto R = tr.topLeftCorner<3, 3>();
6767
R.applyOnTheRight(R2);

src/candlewick/multibody/RobotScene.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ void RobotScene::initCompositePipeline(const MeshLayout &layout) {
226226

227227
pipelines.wboitComposite = SDL_CreateGPUGraphicsPipeline(device(), &desc);
228228
if (!pipelines.wboitComposite) {
229-
terminate_with_message(
230-
std::format("Failed to create WBOIT pipeline: %s", SDL_GetError()));
229+
terminate_with_message("Failed to create WBOIT pipeline: %s",
230+
SDL_GetError());
231231
}
232232
}
233233

@@ -279,7 +279,8 @@ void RobotScene::loadModels(const pin::GeometryModel &geom_model,
279279
m_config.shadow_config);
280280
shadows_configured = true;
281281
}
282-
this->initCompositePipeline(layout);
282+
if (!pipelines.wboitComposite)
283+
this->initCompositePipeline(layout);
283284
}
284285
}
285286
m_initialized = true;

0 commit comments

Comments
 (0)