I am building a physics simulation engine and editor in Windows. I want to build the editor part using Qt and I want to run the engine using SDL with OpenGL.
My first idea was to build the editor using only Qt and share as much code with the engine (the resource manager, the renderer, the maths). But, I would also like to be able to run the simulation inside the editor. This means I also have to share the simulation code which uses SDL threads.
So, my question is this: Is there a way to have an the render OpenGL to a Qt window by using SDL?
I have read on the web that it might be possible to supply SDL with a window handle in which to render. Anybody has experience dong that?
Also, the threaded part of the simulator might pose a problem since it uses SDL threads.
This is a simplification of what I do in my project. You can use it just like an ordinary widget, but as you need, you can using it's m_Screen object to draw to the SDL surface and it'll show in the widget :)
#include "SDL.h"
#include <QWidget>
class SDLVideo : public QWidget {
Q_OBJECT
public:
SDLVideo(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), m_Screen(0){
setAttribute(Qt::WA_PaintOnScreen);
setUpdatesEnabled(false);
// Set the new video mode with the new window size
char variable[64];
snprintf(variable, sizeof(variable), "SDL_WINDOWID=0x%lx", winId());
putenv(variable);
SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
// initialize default Video
if((SDL_Init(SDL_INIT_VIDEO) == -1)) {
std:cerr << "Could not initialize SDL: " << SDL_GetError() << std::endl;
}
m_Screen = SDL_SetVideoMode(640, 480, 8, SDL_HWSURFACE | SDL_DOUBLEBUF);
if (m_Screen == 0) {
std::cerr << "Couldn't set video mode: " << SDL_GetError() << std::endl;
}
}
virtual ~SDLVideo() {
if(SDL_WasInit(SDL_INIT_VIDEO) != 0) {
SDL_QuitSubSystem(SDL_INIT_VIDEO);
m_Screen = 0;
}
}
private:
SDL_Surface *m_Screen;
};
Hope this helps
Note: It usually makes sense to set both the min and max size of this widget to the SDL surface size.
While you might get it to work like first answer suggest you will likely run into problems due to threading. There is no simple solutions when it comes to threading, and here you would have SDL Qt and OpenGL mainloop interacting. Not fun.
The easiest and sanest solution would be to decouple both parts. So that SDL and Qt run in separate processes and have them use some kind of messaging to communicate (I'd recommend d-bus here ). You can have SDL render into borderless window and your editor sends commands via messages.
Rendering onto opengl from QT is trivial (and works very well)
No direct experience of SDL but there is an example app here about mixing them.
http://www.devolution.com/pipermail/sdl/2003-January/051805.html
There is a good article about mixing QT widgewts directly with the opengl here
http://doc.trolltech.com/qq/qq26-openglcanvas.html a bit beyond what you strictly need but rather clever!
Related
I am currently trying to get a Qt 5 window and QOpenGLContext working with GLEW. Yeah, I know Qt 5 provides its own function wrappings for OpenGL, but since my rendering engine relies on GLEW and supports other window libraries as well, Qt's built-in stuff is not an option.
Now, here is what I got up and running so far:
I sub-classed QWindow and equipped it with a QOpenGLContext. The context is initialized successfully.
After initializing QOpenGLContext, I (again successfully) call glewInit() to initialize GLEW.
I am now able to render geometry to the default framebuffer in the exact same way as I do it for other window frameworks (GLFW, to be more precise).
Here comes the tricky part: I am using one of OpenGL's uniform buffer objects to transfer light data to the GPU. As soon as I call glBufferData() to initially fill it, I get a segmentation fault. When using my GLFW-based implementation and context initialization, everything works fine. I know that this kind of behavior can be expected for insufficiently initialized OpenGL contexts, but again, setting up QOpenGLContext and calling glewInit() seems to work just fine.
Here is some code to show what I'm trying to do...
QtWindow::QtWindow(QWindow *parent)
: QWindow(parent) {
setSurfaceType(QWindow::OpenGLSurface);
QSurfaceFormat format;
format.setVersion(4,5);
format.setOption(QSurfaceFormat::DeprecatedFunctions);
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
format.setProfile(QSurfaceFormat::CoreProfile);
setFormat(format);
}
This should be sufficient to later on get a context of the format I desire. Now, just before the first frame is rendered, I set up the context and GLEW...
void QtWindow::init_context() {
if (!initialized_) {
context_handler_.init(this);
initialized_ = true;
glewExperimental = GL_TRUE;
auto e = glewInit();
if (e != GLEW_OK) {
std::cout << "Failed to initialize glew: "
<< glewGetErrorString(e) << std::endl;
}
glGetError();
}
}
I use a small helper class for initializing QOpenGLContext as I need to prevent Qt from un-defining GLEW macros:
void QtContextHandler::init(QWindow* parent) {
if (!qt_context_) {
qt_context_ = new QOpenGLContext(parent);
qt_context_->setFormat(parent->requestedFormat());
if (qt_context_->create()) {
auto format(qt_context_->format());
std::cout << "Initialized Qt OpenGL context "
<< format.majorVersion() << "."
<< format.minorVersion() << " successfully."
<< std::endl;
qt_context_->makeCurrent(parent);
} else {
std::cout << "Failed to initialize Qt OpenGL context!"
<< std::endl;
}
}
}
Here is what I do for setting up the light UBO and what crashes when OpenGL is initialized as shown above. I am using oglplus as a GL wrapper, but since it wraps OpenGL's functions quite tightly, you should get the idea:
ubo_.Bind(ogl::Buffer::Target::Uniform);
oglplus::Buffer::Data(oglplus::Buffer::Target::Uniform, sizeof(data), &data, oglplus::BufferUsage::DynamicDraw);
Has anyone tried similar approaches and can share their experience? I would appreciate any help since I'm stuck trying to figure out what I am doing wrong. Again: The initialization seems to run smoothly and I am even able to create VBOs/VAOs/IBOs for rendering meshes! Only creating the UBO causes a segmentation fault.
EDIT:
Okay, here are some new insights. First of all, the segmentation fault only occurs if the uploaded data exceeds a certain size (~90 bytes). In other words, I can render a scene with the Qt-created context using exactly one custom light source. When querying GL_MAX_UNIFORM_BLOCK_SIZE though, the driver tells me that 64KB are available for uniform blocks (the same holds for GLFW-created contexts). Does anyone have an idea on what could possibly go wrong?
Okay, just in case anyone encounters similar difficulties: I managed to get it working by uploading the UBO data using glBufferStorage instead of glBufferData. The former is used for creating buffers of immutable size, which is sufficient for my purposes. Still, I don't know what went wrong with glBufferData and whether it is a bug in Qt or I initialized the context incorrectly.
Legacy QGLWidget could be integrated with rendering libraries (like SFML) by passing winId() result to the rendering library. But I can't make QOpenGLWidget work that way. After making it MainWindow's central widget I get series of warnings like QOpenGLWidget cannot be used as a native child widget. Consider setting Qt::AA_DontCreateNativeWidgetAncestors and Siblings.. Furthermore, the documentation says "QGLWidget on the other hand uses a native window and surface. (...) QOpenGLWidget avoids this by not creating a separate native window.". Can QOpenGLWidget be integrated with third party OpenGL software at all, or is it unsupported now?
You need to create a QWindow, initialize it and integrate into application with QWidget::createWindowContainer
class MyNativeWindow : QWindow
{
MyNativeWindow() : QWindow
{
setSurfaceType(QWindow::OpenGLSurface);
}
};
MyNativeWindow *nativeW = new MyNativeWindow();
QWidget *w = QWidget::createWindowContainer( nativeW );
// Use w as a simple QWidget
In some cases you don't need to use winId to get HWND. It is enough to know OpenGL context id. For custom gl context manipulation you may use QOpenGLContext class.
Be careful, because if your third party libraries will create native windows (in OS X) by themselves, you will have a lot of bugs with Qt. We are tied to fix bugs in our project. (Undockable docks, keyboard focus lost, impossibility of opening menus, errors with fullscreen etc.)
You may look at this code sample. And a custom context code sample.
I'm trying to make a game on visual studio 2012. I'm using SDL and I've set everything up correctly due to this tutorial: http://sdltutorials.com/sdl-tutorial-basics
I did look for other solutions on google: (1) place the image files in the project file. And, (2) place the images with the .exe program. A window does show but its black with no images. Both of these solutions failed. I'm on the edge of giving up on using visual studio 2012 for anything now. As for the code, I got the sources at the link I posted above. Thanks :)
EDIT:
When I build the project, its a success.
Im also using SDL-devel-1.2.15/SDL version 1.2.15.
My os system is windows 8.1 if that helps.
Here's the code for a short version of this example:
#include "SDL.h"
int main( int argc, char* args[] )
{
//The images
SDL_Surface* hello = NULL;
SDL_Surface* screen = NULL;
//Start SDL
SDL_Init( SDL_INIT_EVERYTHING );
//Set up screen
screen = SDL_SetVideoMode( 640, 480, 32, SDL_SWSURFACE );
//Load image
hello = SDL_LoadBMP( "hello.bmp" );
//Apply image to screen
SDL_BlitSurface( hello, NULL, screen, NULL );
//Update Screen
SDL_Flip( screen );
//Pause
SDL_Delay( 2000 );
//Free the loaded image
SDL_FreeSurface( hello );
//Quit SDL
SDL_Quit();
return 0;
}
The image shows. It was the code example from the website I posted. Can anyone find out what's wrong with his code(not the code I posted here. The code examples I shared in the website)?
Can anyone find out what's wrong with his code(not the code I posted here.
The code in tutorial (Win32_Sources.zip) you linked never calls SDL_Flip So even if you DO draw something on screen, you'll never see it.
Another problem is that this dude hasn't read SDL documentation, and attempts to free framebuffer (allocated with SDL_SetVideoMode) with SDL_FreeSurface. According to documentation, you shouldn't do that, because framebuffer is released by SDL_Quit.
(opinion)In addition to that I think his coding style is rather very poor - the has that "let's make a class for everything"(i.e. he loves OOP too much) problem, that some newbies have. Making separate *.cpp for a single function doesn't look like a good idea to me (it is 10..15 lines of code, for ****'s sake), I don't quite agree with his choice of CodeBlocks for project's IDE/build system, the code also kinda reminds me of MFC or Microsoft coding examples (where classes are named CSomething, etc). Overall, the guy looks like another newbie me so I don't think learning from him is a good idea.(/opinion)
I'd recommend to read SDL documentation instead of following tutorials (like this one). SDL is rather simple by itself, and its documentation contains short/simple examples which are normally written in C, which means there will be no OOP madness as in this guy's example.
For a build system or IDE on windows platform I'd suggest to use Visual Studio express (instead of codeblocks), however you should also learn some cross-platform build tool like CMake or qMake (part of Qt 4 or Qt 5). Learning those tools will make your life easier should you ever decide to transition to linux or something similar.
For a realtively good/clean C++ coding style, you should take a look at Qt 4/5 source code, tutorials and examples. Qt will be also useful shall you ever need GUI toolkit. Also consider trying OpenGL. Pure SDL is fine if you want to do MS-Dos style demos or software renderer, but OpenGL can do certain things faster.
You might consider upgrading to SDL2 and using a different set of tutorials like these that don't look too bad: http://twinklebeardev.blogspot.com/p/sdl-20-tutorial-index.html
If not you can use the same GetCurrentDirectory code to figure out what directory it is expecting the file to be in.
Here's a simple SDL2 example that loads and displays a bitmap. You can put breakpoints in the error checking code so you can see what's wrong if it fails. Make sure you're building a console style program so the errors have somewhere to print.
#include <Windows.h>
#include <iostream>
#include <string>
#include "SDL.h"
#pragma comment(lib, "sdl2.lib")
#pragma comment(lib, "sdl2main.lib")
int main(int argc, char* args[])
{
const std::string bmpFilename("test.bmp");
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window* window = SDL_CreateWindow("Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
if(!window)
{
std::cerr << "Could not create window!\n";
return 0;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if(!renderer)
{
std::cerr << "Could not create renderer!\n";
return 0;
}
SDL_Surface* bmp = SDL_LoadBMP(bmpFilename.c_str());
if(!bmp)
{
char workingDirectory[_MAX_PATH];
GetCurrentDirectory(sizeof(workingDirectory), workingDirectory);
std::string fullPath(std::string(workingDirectory) + "\\" + bmpFilename);
std::cerr << "Could not load " << fullPath << "!\n";
return 0;
}
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, bmp);
if(!texture)
{
std::cerr << "Could not create texture!\n";
return 0;
}
SDL_FreeSurface(bmp);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
SDL_Delay(2000);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
I know you can do the same in lrrlicht, but I want to use SDL code/ functions to draw text, images inside Irrlicht (to handle 2d) and use Irrlicht to do the hardcore 3D thing, how can you apply text or images from sdl to this Irrlicht Engine, can you show me simple code, so that I can understand?
In the SDL you can do such:
// I start by declare the SDL video Name
SDL_Surface *screen;
// set the video mode:
screen = SDL_SetVideoMode(640, 480, 32, SDL_DOUBLEBUF | SDL_FULLSCREEN); if (screen == NULL) { printf("Unable to set video mode: %s\n", SDL_GetError()); return 1; }
// I can now display data, image, text on the screen by using the declared SDL video Name " screen "
SDL_BlitSurface(my_image, &src, screen, &dest);
If you are using / targeting Windows , and are a little familiar with the WinApi, you may be able to use SDL with Irrlicht by running both inside a Win32 Window, using a combination of the SDL Window ID Hack (also see this thread) and running Irrlicht in a Win32 Window. The only problems you may face is that running SDL in a Win32 Window is extremely difficult and unpredictable.
You may be also be able to achieve a similar result using GTK+ (for cross-platform purposes), but I have personally never succeeded in running SDL or Irrlicht in a GTK+ Window.
Also, if you want a light graphics and media library like SDL , may I suggest SFML. It can run in Win32 and X/11 windows without any problems and may easily work with Irrlicht.
I'm trying to create an SDL drawing canvas inside of a simple QT4 window, following the information provided in the SDL wiki and in another question on this site. The project is an NES emulator using QT and SDL that a friend and I decided we wanted to try creating.
Currently, I have a main_window class that will contain the SDL widget, menus that I set up, and probably other stuff as the project develops. The SDL widget I'm creating is called rom_canvas and inherits from QWidget. So far, I'm able to set the SDL_WINDOWID environment variable and I seem to be able to interact with the widget in that I can set and get its geometry and see that it is in fact "visible", but nothing actually shows up in the window.
I don't have any experience with QT4 and SDL until now and don't have a ton of C++ experience, so I could be missing something obvious.
Here's the rom_canvas class:
#include "rom_canvas.hpp"
#include <iostream>
#include <cstdlib>
#include <QString>
rom_canvas::rom_canvas(QWidget *parent)
: QWidget(parent)
{
parent->setAttribute(Qt::WA_PaintOnScreen);
parent->setAttribute(Qt::WA_OpaquePaintEvent);
// setAttribute(Qt::WA_PaintOnScreen);
// setAttribute(Qt::WA_OpaquePaintEvent);
setUpdatesEnabled(false);
// a hack I found online to get the SDL surface to appear in our own window
QString id;
id.setNum(parent->winId());
setenv("SDL_WINDOWID", id.toAscii().data(), 1);
SDL_InitSubSystem(SDL_INIT_VIDEO);
resize(320, 240);
// change constants later
sdl_screen = SDL_SetVideoMode(320, 240, DEFAULT_BPP, SDL_SWSURFACE);
if(!sdl_screen)
std::cout << "couldn't create screen" << std::endl;
SDL_LockSurface(sdl_screen);
SDL_FillRect(sdl_screen, NULL, 0x00FF0000);
SDL_UnlockSurface(sdl_screen);
SDL_UpdateRect(sdl_screen, 0, 0, 0, 0);
}
rom_canvas::~rom_canvas()
{
// do NOT release sdl_screen here; that's done when SDL_Quit() is called in main().
}
// this method is a protected slot
void rom_canvas::test()
{
std::cout << "rom_canvas test" << std::endl;
SDL_LockSurface(sdl_screen);
SDL_FillRect(sdl_screen, NULL, 0x00FF0000);
SDL_UnlockSurface(sdl_screen);
SDL_UpdateRect(sdl_screen, 0, 0, 0, 0);
}
And here's the main_window constructor:
main_window::main_window(QWidget *parent)
: QMainWindow(parent)
{
canvas = new rom_canvas(this);
setCentralWidget(canvas);
canvas->setGeometry(100, 100, 100, 100);
canvas->show();
canvas->update();
// Add File Menu
open_action = new QAction(tr("&Open..."), this);
open_action->setShortcuts(QKeySequence::Open);
open_action->setStatusTip(tr("Open a new ROM"));
connect(open_action, SIGNAL(triggered()), this, SLOT(select_rom()));
exit_action = new QAction(tr("E&xit"), this);
exit_action->setStatusTip(tr("Exit nesT"));
connect(exit_action, SIGNAL(triggered()), /*QApplication::instance()*/canvas, SLOT(/*quit()*/test()));
// Remember to change this back!!
file_menu = menuBar()->addMenu(tr("&File"));
file_menu->addAction(open_action);
file_menu->addAction(exit_action);
rom_dir = QDir::homePath();
}
The code's a bit messy since I've been trying things to get this to work. Any help is of course greatly appreciated.
I guess you already know, but the SDL_WINDOWID trick probably isn't portable - I am pretty sure it won't work on Mac. I am curious what you want SDL for, in this scenario - if you simply want a pixel-addressable image buffer (or several), use QPixmap / QImage and stick to pure Qt. Or if you want the SDL sprite features, I'd suggest having SDL composite to an in-memory buffer attached to a QImage, and then draw that to a Qt widget. (Or use QImages as sprites, and use the OpenGL QPainter backend)
Internally, Qt works pretty hard to use platform-native code to do image format conversion, so even though this sounds fairly copy-heavy, I think it will be sufficiently fast, and much more robust and portable than the SDL_WINDOWID trick.
So I think I got it to at least display something. After playing with the code for a little bit, I ended up commenting out the setAttribute methods at the top of the file and it seems to be working. Also, the setUpdatesEnabled method didn't seem to have an impact, so I removed that since I only had it in there when trying to get this to work.
Below is a picture of it in action. I don't yet have code to refresh the surface, so I have to select a menu option to draw it for now. It does appear that the menu bar is cutting top of the surface off, so I'll have to figure out how to reposition it later.
Hopefully, this helps someone.
SDL canvas http://img18.imageshack.us/img18/3453/nestsdl.png