453 lines
14 KiB
C++
453 lines
14 KiB
C++
#define STB_IMAGE_IMPLEMENTATION
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
|
|
#include <efsw/FileSystem.hpp>
|
|
#include <efsw/System.hpp>
|
|
#include <efsw/efsw.hpp>
|
|
#include <iostream>
|
|
#include <signal.h>
|
|
#include <cstring>
|
|
#include "stb_image.h"
|
|
#include "stb_image_write.h"
|
|
|
|
#include "bella_sdk/bella_engine.h" // Core rendering engine
|
|
#include "bella_sdk/bella_scene.h" // Scene management
|
|
#include "dl_core/dl_logging.h" // Logging utilities
|
|
#include <thread> // For std::this_thread (C++ standard library)
|
|
#include <chrono> // For timing operations (C++ standard library)
|
|
#include <cstdio> // For freopen
|
|
#include <regex>
|
|
|
|
|
|
std::string directory_path = ".";
|
|
bool STOP = false;
|
|
dl::bella_sdk::Engine engine;
|
|
std::string fileToRender = "";
|
|
bool pendingRender = false;
|
|
int width = 800;
|
|
int height = 600;
|
|
|
|
//void flip_image_horizontally(unsigned char* data, int width, int height, int channels);
|
|
void flip_image_vertically(unsigned char* data, int width, int height, int channels);
|
|
|
|
|
|
void sigend( int ) {
|
|
std::cout << std::endl << "close file monitoring" << std::endl;
|
|
STOP = true;
|
|
}
|
|
|
|
|
|
/*
|
|
* MyEngineObserver Class
|
|
* This class receives callbacks from the Bella rendering engine to track rendering progress.
|
|
* It implements the EngineObserver interface and provides methods to:
|
|
* - Handle render start/stop events
|
|
* - Track rendering progress
|
|
* - Handle error conditions
|
|
* - Store and retrieve the current progress state
|
|
*/
|
|
struct MyEngineObserver : public dl::bella_sdk::EngineObserver
|
|
{
|
|
public:
|
|
// Called when a rendering pass starts
|
|
void onStarted(dl::String pass) override
|
|
{
|
|
//std::cout << "Started pass " << pass.buf() << std::endl;
|
|
// dl::logInfo("Started pass %s", pass.buf());
|
|
}
|
|
|
|
// Called to update the current status of rendering
|
|
//void onStatus(String pass, String status) override
|
|
//{
|
|
// logInfo("%s [%s]", status.buf(), pass.buf());
|
|
//}
|
|
|
|
// Called to update rendering progress (percentage, time remaining, etc)
|
|
void onProgress(dl::String pass, dl::bella_sdk::Progress progress) override
|
|
{
|
|
//std::cout << progress.toString().buf() << std::endl;
|
|
setString(new std::string(progress.toString().buf()));
|
|
//dl::logInfo("%s [%s]", progress.toString().buf(), pass.buf());
|
|
}
|
|
|
|
//void onImage(String pass, Image image) override
|
|
//{
|
|
// logInfo("We got an image %d x %d.", (int)image.width(), (int)image.height());
|
|
//}
|
|
|
|
// Called when an error occurs during rendering
|
|
void onError(dl::String pass, dl::String msg) override
|
|
{
|
|
//dl::logError("%s [%s]", msg.buf(), pass.buf());
|
|
}
|
|
|
|
// Called when a rendering pass completes
|
|
void onStopped(dl::String pass) override
|
|
{
|
|
// dl::logInfo("Stopped %s", pass.buf());
|
|
std::atomic_bool active_render = false;
|
|
}
|
|
|
|
// Returns the current progress as a string
|
|
std::string getProgress() const {
|
|
std::string* currentProgress = progressPtr.load();
|
|
if (currentProgress) {
|
|
return *currentProgress;
|
|
}
|
|
else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// Cleanup resources in destructor
|
|
~MyEngineObserver() {
|
|
setString(nullptr);
|
|
}
|
|
private:
|
|
// Thread-safe pointer to current progress string
|
|
std::atomic<std::string*> progressPtr{ nullptr };
|
|
|
|
// Helper function to safely update the progress string
|
|
void setString(std::string* newStatus) {
|
|
std::string* oldStatus = progressPtr.exchange(newStatus);
|
|
delete oldStatus; // Clean up old string if it exists
|
|
}
|
|
};
|
|
|
|
MyEngineObserver engineObserver;
|
|
|
|
//void render_thread(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) {
|
|
// // Construct the full path once
|
|
// dl::String fullPath = dl::String(directory_path.c_str()) + "/" + dl::String(selected_name.c_str());
|
|
//
|
|
// // Load and then Read using the SAME full path
|
|
// engine.loadScene(fullPath);
|
|
// if (!engine.scene().read(fullPath)) {
|
|
// std::cout << "Failed to read scene: " << selected_name << std::endl;
|
|
// return;
|
|
// }
|
|
//
|
|
// //dl::String belPath = dl::String(directory_path.c_str()) + "\\";
|
|
// //engine.loadScene(belPath + dl::String(selected_name.c_str()));
|
|
//
|
|
// //if (!engine.scene().read(dl::String(selected_name.c_str()))) return;
|
|
//
|
|
// dl::bella_sdk::Node bPass = engine.scene().beautyPass();
|
|
//
|
|
// // Setup the outputImagePath node as we did in the synchronous version
|
|
// dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath");
|
|
// pathNode["dir"] = dl::String(directory_path.c_str());
|
|
// pathNode["name"] = "render_result";
|
|
// pathNode["ext"] = ".png";
|
|
// // In render_thread
|
|
// pathNode["dir"] = dl::String("../renders/");
|
|
// bPass["overridePath"] = pathNode;
|
|
// bPass["timeLimit"] = 30.0f;
|
|
//
|
|
// engine.start();
|
|
//
|
|
// // While rendering, sleep and then tell the UI to refresh
|
|
// while (engine.rendering()) {
|
|
// std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
// // This is the key to seeing progress while it's "asynchronous"
|
|
// //screen.PostEvent(ftxui::Event::Custom);
|
|
// //std::cout << "rendering...";
|
|
// }
|
|
// std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Finalize save
|
|
//}
|
|
|
|
void do_sync_render(std::string fileToRender)
|
|
{
|
|
// This removes any trailing slashes from directory_path to prevent the \/ issue
|
|
if (!directory_path.empty() && (directory_path.back() == '/' || directory_path.back() == '\\')) {
|
|
directory_path.pop_back();
|
|
}
|
|
// 1. Force the path to look in the 'test' folder specifically
|
|
std::string fullPathStr = directory_path + "/" + fileToRender;
|
|
dl::String fullPath(fullPathStr.c_str());
|
|
|
|
std::cout << ">>> ATTEMPTING TO READ: " << fullPathStr << std::endl;
|
|
|
|
engine.loadScene(fullPath);
|
|
if (!engine.scene().read(fullPath)) {
|
|
std::cout << ">>> ERROR: Bella could not read scene file!" << std::endl;
|
|
return;
|
|
}
|
|
|
|
// 2. Setup the output path using a relative path that goes OUTSIDE 'test'
|
|
dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath");
|
|
pathNode["dir"] = dl::String(directory_path.c_str());
|
|
//pathNode["dir"] = dl::String(".");
|
|
pathNode["name"] = dl::String("render_result");
|
|
pathNode["ext"] = dl::String(".png");
|
|
|
|
// Get the global settings node instead of the beautyPass node
|
|
dl::bella_sdk::Node settings = engine.scene().settings();
|
|
dl::bella_sdk::Node cam = settings["camera"].asNode();
|
|
auto inputs = settings.inputs(); // This gets all valid attribute names
|
|
for (const auto& input : inputs) {
|
|
std::cout << "Valid attribute: " << input.name() << std::endl;
|
|
}
|
|
|
|
if (cam) {
|
|
auto inputs = cam.inputs();
|
|
for (const auto& input : inputs) {
|
|
// This will print every valid attribute name for YOUR camera
|
|
std::cout << "Camera Attribute: " << input.name() << std::endl;
|
|
}
|
|
|
|
if (cam) {
|
|
std::cout << ">>> Set resolution to " << width <<"x" << height << " on camera : " << cam.name() << std::endl;
|
|
float fWidth = static_cast<float>(width);
|
|
float fHeight = static_cast<float>(height);
|
|
cam["resolution"] = dl::Vec2{ fWidth, fHeight };
|
|
}
|
|
}
|
|
else {
|
|
std::cout << ">>> ERROR: No camera found in settings!" << std::endl;
|
|
}
|
|
|
|
dl::bella_sdk::Node bPass = engine.scene().beautyPass();
|
|
|
|
|
|
bPass["overridePath"] = pathNode;
|
|
|
|
std::cout << ">>> STARTING BELLA ENGINE..." << std::endl;
|
|
engine.start();
|
|
|
|
// 3. Monitor progress
|
|
while (engine.rendering()) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
std::cout << "rendering..." << std::endl;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Give Windows time to finish the file write
|
|
std::cout << ">>> RENDER FINISHED. Saved in current directory" << std::endl;
|
|
}
|
|
|
|
void render_thread(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) {
|
|
// This removes any trailing slashes from directory_path to prevent the \/ issue
|
|
if (!directory_path.empty() && (directory_path.back() == '/' || directory_path.back() == '\\')) {
|
|
directory_path.pop_back();
|
|
}
|
|
// 1. Force the path to look in the 'test' folder specifically
|
|
std::string fullPathStr = directory_path + "/" + selected_name;
|
|
dl::String fullPath(fullPathStr.c_str());
|
|
|
|
std::cout << ">>> ATTEMPTING TO READ: " << fullPathStr << std::endl;
|
|
|
|
engine.loadScene(fullPath);
|
|
if (!engine.scene().read(fullPath)) {
|
|
std::cout << ">>> ERROR: Bella could not read scene file!" << std::endl;
|
|
return;
|
|
}
|
|
|
|
// 2. Setup the output path using a relative path that goes OUTSIDE 'test'
|
|
// This assumes your 'renders' folder is in x64/release/renders
|
|
dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath");
|
|
pathNode["dir"] = dl::String("../renders/");
|
|
pathNode["name"] = dl::String("render_result");
|
|
pathNode["ext"] = dl::String(".png");
|
|
|
|
dl::bella_sdk::Node bPass = engine.scene().beautyPass();
|
|
bPass["overridePath"] = pathNode;
|
|
|
|
std::cout << ">>> STARTING BELLA ENGINE..." << std::endl;
|
|
engine.start();
|
|
|
|
// 3. Monitor progress
|
|
while (engine.rendering()) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
std::cout << "rendering..." << std::endl;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Give Windows time to finish the file write
|
|
std::cout << ">>> RENDER FINISHED. Check x64/release/renders/" << std::endl;
|
|
}
|
|
|
|
|
|
/// Processes a file action
|
|
class UpdateListener : public efsw::FileWatchListener {
|
|
public:
|
|
UpdateListener() {}
|
|
|
|
std::string getActionName( efsw::Action action ) {
|
|
switch ( action ) {
|
|
case efsw::Actions::Add:
|
|
|
|
return "Add";
|
|
case efsw::Actions::Modified:
|
|
return "Modified";
|
|
case efsw::Actions::Delete:
|
|
return "Delete";
|
|
case efsw::Actions::Moved:
|
|
return "Moved";
|
|
default:
|
|
return "Bad Action";
|
|
}
|
|
}
|
|
|
|
void handleFileAction( efsw::WatchID watchid, const std::string& dir,
|
|
const std::string& filename, efsw::Action action,
|
|
std::string oldFilename = "" ) override {
|
|
std::cout << "Watch ID " << watchid << " DIR ("
|
|
<< dir + ") FILE (" +
|
|
( oldFilename.empty() ? "" : "from file " + oldFilename + " to " ) +
|
|
filename + ") has event "
|
|
<< getActionName( action ) << std::endl;
|
|
|
|
if (action == efsw::Actions::Add || action == efsw::Actions::Modified) {
|
|
directory_path = dir;
|
|
std::string fullPath = dir + filename;
|
|
int width, height, channels;
|
|
if (filename.substr(filename.length() - 3) == "bsz" ) {
|
|
std::cout << "Converting " << filename << " to a PNG" << std::endl;
|
|
// 2. Start the thread and DETACH it
|
|
// Note: We pass 'screen' as a reference so render_thread can call PostEvent
|
|
fileToRender = filename;
|
|
pendingRender = true;
|
|
//std::thread(render_thread, std::ref(engine), std::ref(engineObserver), filename).detach();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
efsw::WatchID handleWatchID( efsw::WatchID watchid ) {
|
|
switch ( watchid ) {
|
|
case efsw::Errors::FileNotFound:
|
|
case efsw::Errors::FileRepeated:
|
|
case efsw::Errors::FileOutOfScope:
|
|
case efsw::Errors::FileRemote:
|
|
case efsw::Errors::WatcherFailed:
|
|
case efsw::Errors::Unspecified: {
|
|
std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl;
|
|
break;
|
|
}
|
|
default: {
|
|
std::cout << "Added WatchID: " << watchid << std::endl;
|
|
}
|
|
}
|
|
|
|
return watchid;
|
|
}
|
|
|
|
void flip_image_vertically(unsigned char* data, int width, int height, int channels) {
|
|
// ... (your vertical flip implementation from before)
|
|
int row_stride = width * channels;
|
|
unsigned char* temp_row = new unsigned char [row_stride];
|
|
for (int y = 0; y < height / 2; ++y) {
|
|
unsigned char* top_row = data + y * row_stride;
|
|
unsigned char* bottom_row = data + (height - 1 - y) * row_stride;
|
|
std::memcpy(temp_row, top_row, row_stride);
|
|
std::memcpy(top_row, bottom_row, row_stride);
|
|
std::memcpy(bottom_row, temp_row, row_stride);
|
|
}
|
|
delete[] temp_row;
|
|
}
|
|
|
|
//int main( int argc, char** argv ) {
|
|
#include "dl_core/dl_main.inl"
|
|
#include "dl_core/dl_args.h"
|
|
int DL_main(dl::Args& args) {
|
|
signal( SIGABRT, sigend );
|
|
signal( SIGINT, sigend );
|
|
signal( SIGTERM, sigend );
|
|
// 1. Initialize engine and observer locally within the logic block
|
|
|
|
engine.subscribe(&engineObserver);
|
|
|
|
std::cout << "Press ^C to exit demo" << std::endl;
|
|
|
|
bool commonTest = true;
|
|
bool useGeneric = false;
|
|
std::string path;
|
|
|
|
args.add("wd", "watchdir", "", "mode file watch");
|
|
args.add("w", "width", "800", "render width");
|
|
args.add("h", "height", "600", "render height");
|
|
|
|
/*if (argc >= 2) {
|
|
path = std::string( argv[1] );
|
|
|
|
if ( efsw::FileSystem::isDirectory( path ) ) {
|
|
commonTest = false;
|
|
}
|
|
|
|
if ( argc >= 3 ) {
|
|
if ( std::string( argv[2] ) == "true" ) {
|
|
useGeneric = true;
|
|
}
|
|
}
|
|
}*/
|
|
//std::cout << "args: " << args. << std::endl;
|
|
if (args.have("--watchdir")) {
|
|
path = args.value("--watchdir").buf();
|
|
std::cout << "Watching path: " << path << std::endl;
|
|
if (efsw::FileSystem::isDirectory(path)) {
|
|
commonTest = false;
|
|
useGeneric = true; // Force generic for testing
|
|
}
|
|
}
|
|
|
|
if (args.have("--width")) {
|
|
width = std::stoi(args.value("--width").buf());
|
|
}
|
|
if (args.have("--height")) {
|
|
height = std::stoi(args.value("--height").buf());
|
|
}
|
|
|
|
UpdateListener* ul = new UpdateListener();
|
|
|
|
/// create the file watcher object
|
|
efsw::FileWatcher fileWatcher( useGeneric );
|
|
|
|
fileWatcher.followSymlinks( false );
|
|
fileWatcher.allowOutOfScopeLinks( false );
|
|
|
|
//std::cout << "commonTest: " << commonTest << std::endl;
|
|
if ( commonTest ) {
|
|
std::string CurPath( efsw::System::getProcessPath() );
|
|
|
|
std::cout << "CurPath: " << CurPath.c_str() << std::endl;
|
|
|
|
/// starts watching
|
|
fileWatcher.watch();
|
|
|
|
/// add a watch to the system
|
|
handleWatchID( fileWatcher.addWatch( CurPath , ul, true ) );
|
|
|
|
} else {
|
|
if ( fileWatcher.addWatch( path, ul, true ) > 0 ) {
|
|
fileWatcher.watch();
|
|
/// add a watch to the system
|
|
handleWatchID(fileWatcher.addWatch(path, ul, true));
|
|
std::cout << "Watching directory: " << path.c_str() << std::endl;
|
|
directory_path = path;
|
|
if ( useGeneric ) {
|
|
std::cout << "Using generic backend watcher" << std::endl;
|
|
}
|
|
} else {
|
|
std::cout << "Error trying to watch directory: " << path.c_str() << std::endl;
|
|
std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl;
|
|
}
|
|
}
|
|
|
|
while ( !STOP ) {
|
|
if (pendingRender) {
|
|
// We are now in the main thread.
|
|
// 1. Give the OS a moment to release file locks
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
|
|
// 2. Call render logic (not in a thread)
|
|
// Note: You'll need to move render_thread logic into a regular function
|
|
do_sync_render(fileToRender);
|
|
|
|
pendingRender = false;
|
|
}
|
|
efsw::System::sleep( 100 );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|