aboutsummaryrefslogtreecommitdiff
path: root/src/rendering/render_path.cpp
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-24 03:57:40 +0200
committerhachem <im@hachem.wtf>2026-08-24 03:57:40 +0200
commit59b7c407550d63e997ed1e7bed286be5c332c285 (patch)
tree95f168df75d33326dde486b401c1abf62608ff7c /src/rendering/render_path.cpp
parent7ae22084c74cbe0c127e0ea1c96ec22a1f47d221 (diff)
[feat]: work on render path exporting
Diffstat (limited to 'src/rendering/render_path.cpp')
-rw-r--r--src/rendering/render_path.cpp94
1 files changed, 94 insertions, 0 deletions
diff --git a/src/rendering/render_path.cpp b/src/rendering/render_path.cpp
index 115d182..8cb0328 100644
--- a/src/rendering/render_path.cpp
+++ b/src/rendering/render_path.cpp
@@ -1,10 +1,18 @@
#include "render_path.h"
#include "scene/scene.h"
+#include "core/log.h"
+
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "stb_image_write.h"
#include <glm/gtc/matrix_transform.hpp>
#include <algorithm>
#include <cmath>
+#include <filesystem>
+#include <ctime>
+#include <cstdio>
+#include <vector>
namespace Donut
{
@@ -90,4 +98,90 @@ namespace Donut
// Backend-specific HDRI/GPU resource cleanup is the device's job (see e.g.
// the OpenGL device clearing the HDRIManager texture cache on shutdown).
}
+
+ // Portable Float Map: a raw RGB float image (the actual physical values). Its
+ // raster is bottom-up; -1.0 scale flags little-endian.
+ static auto write_pfm(const std::string& path, int w, int h, const std::vector<float>& rgba) -> bool
+ {
+ std::FILE* f = std::fopen(path.c_str(), "wb");
+ if (!f) return false;
+ std::fprintf(f, "PF\n%d %d\n-1.0\n", w, h);
+ std::vector<float> rgb((size_t)w * 3);
+ for (int y = h - 1; y >= 0; --y)
+ {
+ const float* src = &rgba[(size_t)y * w * 4];
+ for (int x = 0; x < w; ++x) { rgb[x*3+0] = src[x*4+0]; rgb[x*3+1] = src[x*4+1]; rgb[x*3+2] = src[x*4+2]; }
+ std::fwrite(rgb.data(), sizeof(float), (size_t)w * 3, f);
+ }
+ std::fclose(f);
+ return true;
+ }
+
+ // CSV grid of the scalar value (the R channel), one image row per text line.
+ static auto write_csv(const std::string& path, int w, int h, const std::vector<float>& rgba) -> bool
+ {
+ std::FILE* f = std::fopen(path.c_str(), "wb");
+ if (!f) return false;
+ for (int y = 0; y < h; ++y)
+ {
+ for (int x = 0; x < w; ++x) std::fprintf(f, x ? ",%g" : "%g", rgba[((size_t)y * w + x) * 4]);
+ std::fputc('\n', f);
+ }
+ std::fclose(f);
+ return true;
+ }
+
+ auto RenderPath::export_frame(Scene& scene, const ExportConfig& cfg) -> int
+ {
+ if (!m_device) return 0;
+ const int w = std::max(cfg.width, 1), h = std::max(cfg.height, 1);
+ const bool raw = cfg.format != ExportFormat::Png; // Pfm / Csv want physical values
+ auto target = m_device->create_render_target(
+ w, h, raw ? RHI::Format::RGBA32F : RHI::Format::RGBA8, RHI::Format::None, RHI::Filter::Nearest);
+
+ // Full-quality geodesic view from the sim camera (moving = false = settled).
+ GeodesicView gv;
+ glm::vec3 pos, fwd;
+ if (scene.sim_camera.get_camera_mode() == CameraMode::FPS)
+ { pos = scene.sim_camera.get_position(); fwd = scene.sim_camera.get_forward_direction(); }
+ else
+ { pos = scene.sim_camera.get_orbital_position(); fwd = glm::normalize(scene.sim_camera.get_orbital_target() - pos); }
+ glm::vec3 right = glm::normalize(glm::cross(fwd, glm::vec3(0, 1, 0)));
+ gv.position = pos; gv.right = right; gv.up = glm::cross(right, fwd); gv.forward = fwd;
+ gv.tan_half_fov = (float)tan(glm::radians(scene.black_hole.fov_degrees * 0.5f));
+ gv.aspect = (float)w / (float)h;
+ gv.moving = false;
+ gv.time = 0.0f;
+
+ std::error_code ec; std::filesystem::create_directories(cfg.directory, ec);
+ char stamp[32]; std::time_t t = std::time(nullptr);
+ std::strftime(stamp, sizeof(stamp), "%Y%m%d_%H%M%S", std::localtime(&t));
+ const char* ext = cfg.format == ExportFormat::Pfm ? "pfm" : cfg.format == ExportFormat::Csv ? "csv" : "png";
+
+ int written = 0;
+ std::vector<uint8_t> px8; std::vector<float> pxf;
+ auto do_channel = [&](bool enabled, int channel, const char* name)
+ {
+ if (!enabled) return;
+ m_device->run_offscreen([&](RHI::CommandList& cmd) {
+ m_black_hole_renderer->render_export(cmd, target.get(), gv, scene.black_hole,
+ scene.objects, m_cubemap.get(), channel, raw);
+ });
+ std::string path = cfg.directory + "/donut_" + name + "_" + stamp + "." + ext;
+ bool ok;
+ if (cfg.format == ExportFormat::Png)
+ { m_device->read_render_target(target.get(), px8); ok = stbi_write_png(path.c_str(), w, h, 4, px8.data(), w * 4) != 0; }
+ else
+ {
+ m_device->read_render_target_float(target.get(), pxf);
+ ok = (cfg.format == ExportFormat::Pfm) ? write_pfm(path, w, h, pxf) : write_csv(path, w, h, pxf);
+ }
+ if (ok) { DONUT_INFO("Exported {}", path); ++written; } else DONUT_ERROR("Export failed: {}", path);
+ };
+ do_channel(cfg.color, 0, "color");
+ do_channel(cfg.redshift, 1, "redshift");
+ do_channel(cfg.temperature, 2, "temperature");
+ do_channel(cfg.impact, 3, "impact");
+ return written;
+ }
}