Using GTKmm 3 and Irrlicht

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
chronologicaldot
Competition winner
Posts: 685
Joined: Mon Sep 10, 2012 8:51 am

Re: Using GTKmm 3 and Irrlicht

Post by chronologicaldot »

Ok, so by your suggestions, I got it to render OpenGL. Weird.
So then the problem is with GTK not requesting OpenGL for its window? (Makes me wonder how GLArea worked...)
CuteAlien
Admin
Posts: 9643
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Using GTKmm 3 and Irrlicht

Post by CuteAlien »

I suspect so. Maybe there are other solutions. Like it could always create a child-window for example. Maybe GLArea does that. Or it's possible to change visuals after creating a window (not heard about that and I doubt it, but I'm not that familiar with X11).

Let's hope it works well enough for you for now.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
chronologicaldot
Competition winner
Posts: 685
Joined: Mon Sep 10, 2012 8:51 am

Re: Using GTKmm 3 and Irrlicht

Post by chronologicaldot »

This StackOverflow answer says create a child window, so your guess seems right. I'll have to do some digging to figure that out. In any case, I suppose it's fine for now to have it as a separate window.
Thanks for your help!

In the mean time, I stumbled on a different problem that I sort of fixed. Just rambling now for anyone interested.
GTK likes to sit and wait for input, but Irrlicht needs to be rendered continuously for my needs. I decided to test putting Irrlicht in a second thread. It works for Burnings but locked up for OpenGL on glClearColor() (called by IVideoDriver::beginScene() I presume). Turns out that OpenGL can't be active in more than one thread at a time, at least not without passing around a context (which is "possible" with Irrlicht, but I've already have enough trouble with SExposedVideoData). (A bummer for me because I'd like to use it for more than one frame.) To fix it, I put irr::createDevice in the second thread. This loads windows and allows drawing for both Burnings and OpenGL, but OpenGL crashes the program once a window is closed.

The code is quite different this time...

main.cpp

Code: Select all

/*
 * main.cpp
 *
 *  Created on: Oct 1, 2022
 *      Author: Nic Anderson / chronologicaldot
 */
/*
	The way this works is that Irrlicht is placed on a separate thread from GTK.
	To communicate, instructions are passed from GTK to Irrlicht via a mutex-locked queue.
	Every cycle, the queue is processed by the Irrlicht thread until it is empty.
*/

#include <cstdlib> // For rand()
#include <thread>
#include <atomic>
#include <mutex>
#include <irrlicht.h>
#include <glibmm/ustring.h>
//#include <glibmm/main.h> // For SignalIdle/signal_idle()
#include <glibmm.h>
#include <gtkmm/application.h>
#include <gtkmm/window.h>
#include <gtkmm/button.h>
#include "IrrQueue.h"

class HelloWorld : public Gtk::Window
{
	irr::IrrlichtDevice* irr_device;
	IrrQueue cmd_queue;
	std::mutex is_irr_done_mutex;
	std::mutex close_irr_mutex;
	std::atomic<bool> is_irr_done;
	std::atomic<bool> close_irr;
	std::thread* irr_thread;
	std::thread* irr_thread2;

	Gtk::Button my_button;

public:
	HelloWorld()
		: cmd_queue()
		, is_irr_done_mutex()
		, close_irr_mutex()
		, is_irr_done(false)
		, close_irr(false)
		, irr_thread(nullptr)
		, my_button("Add Cube")
	{
		// Set up the GUI
		set_border_width(10);
		my_button.signal_clicked().connect(sigc::mem_fun(*this,
		              &HelloWorld::onAddCubeButtonClicked));
		add(my_button);
		my_button.show();

		// Window close event
		signal_hide().connect(sigc::mem_fun(*this, &HelloWorld::onWindowHide));
	}

	virtual ~HelloWorld() {}

	void init()
	{
		irr_thread = new std::thread(&HelloWorld::runIrrThread, this);
		irr_thread->detach(); // So we can kill it within itself

			// Works for BurningsVideo, but crashes on close for OpenGL
		//irr_thread2 = new std::thread(&HelloWorld::runIrrThread, this);
		//irr_thread2->detach(); // So we can kill it within itself

		present(); // Bring this Window's X Window to the fore front (above the Irrlicht window).
	}

	void onAddCubeButtonClicked()
	{
		cmd_queue.command_add_cube();
	}

	void closeIrr()
	{
		std::lock_guard<std::mutex> lock(close_irr_mutex);
		close_irr.store(true);
	}

	bool isIrrSetToClose()
	{
		std::lock_guard<std::mutex> lock(close_irr_mutex);
		return close_irr.load();
	}

	void onWindowHide()
	{
		closeIrr();
	}

protected:
	float getRandomF()
	{
		return (float)(rand()%100)/100.f;
	}

	void irrCheckQueue(irr::IrrlichtDevice* _irr_device)
	{
		while ( cmd_queue.has_more_commands() )
		{
			switch( cmd_queue.get_next_command() )
			{
			case IrrQueue::IQ_ADD_CUBE:
				_irr_device->getSceneManager()->addCubeSceneNode(
					1, 0, -1,
					irr::core::vector3df(getRandomF(),0,getRandomF()),
					irr::core::vector3df(getRandomF(),0,getRandomF()),
					irr::core::vector3df(getRandomF()+0.1f,getRandomF()+0.1f,getRandomF()+0.1f),
					irr::scene::ECMT_1BUF_12VTX_NA);
				break;
			default: break;
			}
		}
	}

	int runIrrThread()
	{
		irr::u32 windowWidth = 900, windowHeight = 600;

		irr::SIrrlichtCreationParameters irr_params;
		irr_params.AntiAlias = false;
		irr_params.Bits = 32;
		irr_params.DeviceType = irr::EIDT_X11;
		irr_params.DriverType = irr::video::EDT_OPENGL;
		//irr_params.DriverType = irr::video::EDT_BURNINGSVIDEO;
		//irr_params.EventReceiver = &event_receiver;
		irr_params.Fullscreen = false;
		//irr_params.IgnoreInput = true; // TODO: Test without this too. hm.. Maybe a problem if thread dies.
		irr_params.Stencilbuffer = true;
		irr_params.Vsync = false;
		irr_params.WithAlphaChannel = false;
		irr_params.WindowSize = irr::core::dimension2du(windowWidth,windowHeight);

		irr::IrrlichtDevice* _irr_device = irr::createDeviceEx(irr_params);

		if ( !_irr_device ) return 1;

		// Some scene objects so we can see
		_irr_device->getSceneManager()->addLightSceneNode(
				0, irr::core::vector3df(1,10,2), irr::video::SColor(0xffffffff), 20, 0);

		_irr_device->getSceneManager()->addCameraSceneNode(
				0, irr::core::vector3df(0,5,5), irr::core::vector3df(0,0,0), 1, true);

		while (_irr_device->run())
		{
			if (isIrrSetToClose()) {
				_irr_device->closeDevice();
				break;
			}
			//irr_device->getTimer()->tick(); // Manual control over clock ticking if no irr_device->run()
			_irr_device->getVideoDriver()->beginScene(true, true, irr::video::SColor(0xff0000ff));
			_irr_device->getSceneManager()->drawAll();
			_irr_device->getVideoDriver()->endScene();
			irrCheckQueue(_irr_device);
		}
		_irr_device->drop();
		_irr_device=0;
		return 0;
	}
};

int main(int argc, char* argv[])
{
	Glib::RefPtr<Gtk::Application> app = Gtk::Application::create(argc, argv, "org.gtkmm.example"); // @suppress("Invalid arguments")

	HelloWorld hello_world;

	// Run once GTK has finished its initial setup
	auto hello_world_init = sigc::mem_fun(hello_world, &HelloWorld::init);
	Glib::signal_idle().connect_once(hello_world_init);

	return app->run(hello_world);
}
I use a thread queue that works perfectly when Burnings is used and doesn't even get tested for the OpenGL version.

IrrQueue.h

Code: Select all

/*
 * IrrQueue.h
 *
 *  Created on: Oct 1, 2022
 *      Author: Nic Anderson / chronologicaldot
 */

#include <mutex>

#ifndef IRRQUEUE_H_
#define IRRQUEUE_H_
/*
 * IrrQueue
 * - A mutex-locked push-pop stack. */
class IrrQueue {
public:
	enum Command {
		IQ_NONE =0,
		IQ_ADD_CUBE,
		IQ_COMMAND_COUNT
	};

private:
	struct Node {
		Node* next;
		Command cmd;

		Node(Command c) : next(nullptr), cmd(c) {}
	};
	Node* my_head;
	Node* my_tail;
	std::mutex my_mutex;

public:

	IrrQueue();
	virtual ~IrrQueue();

	// Used by the main thread to send commands to Irrlicht
	void command_add_cube();

	// Used by the Irrlicht thread to get commands from the main thread
	bool has_more_commands();
	Command get_next_command();
};

#endif /* IRRQUEUE_H_ */
IrrQueue.cpp

Code: Select all

/*
 * IrrQueue.cpp
 *
 *  Created on: Oct 1, 2022
 *      Author: Nic Anderson / chronologicaldot
 */

#include "IrrQueue.h"

IrrQueue::IrrQueue()
	: my_head(nullptr)
	, my_tail(nullptr)
	, my_mutex()
{
}

IrrQueue::~IrrQueue()
{
}

void IrrQueue::command_add_cube()
{
	const std::lock_guard<std::mutex> lock(my_mutex);
	Node* n = new Node(IQ_ADD_CUBE);
	if ( my_tail != nullptr )
	{
		my_tail->next = n;
		my_tail = my_tail->next;
		return;
	} // else my_tail == nullptr
	my_head = n;
	my_tail = n;
}

bool IrrQueue::has_more_commands()
{
	const std::lock_guard<std::mutex> lock(my_mutex);
	return my_head != nullptr;
}

IrrQueue::Command IrrQueue::get_next_command()
{
	Command ret;
	const std::lock_guard<std::mutex> lock(my_mutex);
	if (my_head == nullptr)
		return IQ_NONE;
	ret = my_head->cmd;
	Node* drop = my_head;
	my_head = my_head->next;
	if (my_head == nullptr)
		my_tail = nullptr;
	delete drop;
	return ret;
}
CuteAlien
Admin
Posts: 9643
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Using GTKmm 3 and Irrlicht

Post by CuteAlien »

You read my post earlier about simply using a timer? Run it 30 or 60 times per second. No second thread needed.
Or well - my post also explained how to run opengl in another thread. Just 2 lines needed (one on thread start and one on thread stop).
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
chronologicaldot
Competition winner
Posts: 685
Joined: Mon Sep 10, 2012 8:51 am

Re: Using GTKmm 3 and Irrlicht

Post by chronologicaldot »

I forgot your earlier post; I only responded to part of it.
I don't really understand the 2 lines for threading though. Wouldn't creating a device also create the video driver so the context would be the same when setting activateContext()? I guess I'd have to do alittle fiddling to see what works.
The timer is a good idea, but at the moment, I don't know how to do that with GTK. I vaguely recall there's a way, but I can't think of it off the top of my head.
CuteAlien
Admin
Posts: 9643
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Using GTKmm 3 and Irrlicht

Post by CuteAlien »

If you create the device in the thread it probably should also work (I haven't tried that yet). Otherwise the activeContext is for a thread taking over the opengl context so you can use OpenGL in that thread (but no longer in the main thread then - only one gets access).
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
chronologicaldot
Competition winner
Posts: 685
Joined: Mon Sep 10, 2012 8:51 am

Re: Using GTKmm 3 and Irrlicht

Post by chronologicaldot »

Different tangent...

I tried creating a FrameBuffer device to embed in GTK, but for some reason, I can't even get the framebuffer device to start. I'm on Linux, so I would think it would work, but maybe it's not supported?? Or maybe the device creation function doesn't like my parameters... But I can't use different ones, so I'm stuck.
Obviously, I never got to find out if my copy from Irrlicht's render target texture to GDK Pixbuf actually works.

For anyone interested, here's what I did:

Code: Select all

#include <iostream>
#include <gtkmm.h>
#include <irrlicht.h>

class IrrPanel : public Gtk::DrawingArea {
public:
	IrrPanel(const int width, const int height)
		: my_width(width)
		, my_height(height)
		, my_irrDevice(0)
		, my_pixbuf()
		, renderTargetTexture(0)
	{
	}

	virtual ~IrrPanel()
	{
		if ( my_irrDevice ) {
			my_irrDevice->closeDevice();
			my_irrDevice->drop();
			my_irrDevice = 0;
		}
	}

	void init()
	{
		irr::SIrrlichtCreationParameters irrParams;
		irrParams.AntiAlias = 0;
		irrParams.Bits = 32;
		irrParams.DeviceType = irr::EIDT_FRAMEBUFFER;
		irrParams.DriverType = irr::video::EDT_BURNINGSVIDEO;
		irrParams.Fullscreen = false;
		//irrParams.IgnoreInput = true;
		irrParams.Stencilbuffer = false;
		irrParams.WindowResizable = 0;
		irrParams.WindowSize = irr::core::dimension2du(my_width, my_height);
		irrParams.WithAlphaChannel = true;

		my_irrDevice = irr::createDeviceEx(irrParams);

		if (!my_irrDevice) {
			std::cerr << "Error in creating Irrlicht Device";
			return;
		}

		my_irrDevice->getSceneManager()->addCameraSceneNode(
				0, irr::core::vector3df(0,2,-2), irr::core::vector3df(0), 0, true);
	}

	void add_cube_to_scene()
	{
		if (!my_irrDevice)
			return;

		my_irrDevice->getSceneManager()->addCubeSceneNode(
				1, 0, 1, irr::core::vector3df(0), irr::core::vector3df(0,0.2f,0), irr::core::vector3df(1),
				irr::scene::ECMT_1BUF_12VTX_NA);
	}

protected:
	const int my_width;
	const int my_height;
	irr::IrrlichtDevice* my_irrDevice;
	Glib::RefPtr<Gdk::Pixbuf> my_pixbuf;
	irr::video::ITexture* renderTargetTexture;

	bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override
	{
		if (!my_pixbuf || !my_irrDevice)
			return false;

		irr::video::SColor bufferFillColor = 0xff000000;

		my_irrDevice->getVideoDriver()->setRenderTarget(renderTargetTexture, true, true, bufferFillColor);
		//my_irrDevice->run();
		my_irrDevice->getVideoDriver()->beginScene(true, true, bufferFillColor);
		my_irrDevice->getSceneManager()->drawAll();
		my_irrDevice->getVideoDriver()->endScene();

		// lock() should return irr::video::SColor for the FrameBuffer settings I set above
		void* renderData = renderTargetTexture->lock(
				irr::video::E_TEXTURE_LOCK_MODE::ETLM_READ_ONLY, 0, 0,
				irr::video::E_TEXTURE_LOCK_FLAGS::ETLF_FLIP_Y_UP_RTT);

		irr::core::dimension2du rttSize = renderTargetTexture->getSize();
		my_pixbuf = my_pixbuf->create_from_data((const guint8*) renderData, Gdk::COLORSPACE_RGB, true, 32, rttSize.Width, rttSize.Height, renderTargetTexture->getPitch());

		renderTargetTexture->unlock();

		const int location_x = 0;
		const int location_y = 0;

		Gdk::Cairo::set_source_pixbuf(cr, my_pixbuf, location_x, location_y);
		cr->paint();
		return true;
	}
};

class MainWindow : public Gtk::Window {
public:
	MainWindow()
		: my_width(800)
		, my_height(620)
		, my_vbox(Gtk::ORIENTATION_VERTICAL)
		, my_irrPanel(my_width, my_height-80)
		, my_button_addCube("Add Cube")
	{
		set_default_size(my_width, my_height);
		add(my_vbox);
		//my_vbox.pack_start(my_irrPanel);
		my_vbox.pack_end(my_irrPanel);
		my_vbox.pack_end(my_button_addCube, Gtk::PACK_SHRINK);

		my_button_addCube.signal_clicked().connect(sigc::mem_fun(my_irrPanel, &IrrPanel::add_cube_to_scene)); // @suppress("Invalid arguments")
		my_irrPanel.init();
		show_all_children();
	}

protected:
	const int my_width;
	const int my_height;
	Gtk::VBox my_vbox;
	IrrPanel my_irrPanel;
	Gtk::Button my_button_addCube;
};

int main(int argc, char* argv[])
{
	Glib::RefPtr<Gtk::Application> app = Gtk::Application::create(argc, argv, "org.gtkmm.irr_fb_test"); // @Suppress("Invalid arguments")

	MainWindow mainWindow;

	return app->run(mainWindow);
}
NOTE FOR ANYONE COPYING THE ABOVE CODE: The reason this won't work as a FINAL solution is because Gtkmm's on_draw() is only called during refresh, so Irrlicht never gets updated. One guy did figure out that he could update the image from a thread, but it required using Dispatcher to notify the main thread of GTK:
https://stackoverflow.com/questions/542 ... -separated
chronologicaldot
Competition winner
Posts: 685
Joined: Mon Sep 10, 2012 8:51 am

Re: Using GTKmm 3 and Irrlicht

Post by chronologicaldot »

I figured out how to do it now.
See this thread:
viewtopic.php?p=306968
Post Reply