CEGUI, DirectX, Callbacks...

So I wanted to use CEGUI inside Panda and for this particular app I’m OK with only using DirectX (though I will be able to apply the same approach to OpenGL if I want to in the future.) Because I don’t have much time for writing a Panda3D CEGUI renderer, I used CEGUI’s Direct3D9 renderer, butchering my way into the GSG so that Panda would call the CEGUI rendering functions before the call to D3DDevice::EndScene.

I got CEGUI working like a charm now, but I had to add 3 callbacks to accomplish integration, here’s the usage:

  GraphicsStateGuardian *gsg = window->get_graphics_output()->get_gsg();
  gsg->set_render_callback(gui_update);
  gsg->set_pre_reset_callback(gui_pre_reset);
  gsg->set_post_reset_callback(gui_post_reset);

set_render_callback() sets a function that will be called before right before EndScene. The other two are necessary to tell CEGUI that we are resetting the device, otherwise I get a LOSTDEVICE crash. I inserted them before and after the call to Reset() that happens when the window is resized.

I also needed a way to tell CEGUI the pointer to the D3DDevice, so I added a get_device_int() method to the gsg that returns the pointer as an int.

int device = window->get_graphics_output()->get_gsg()->get_device_int();
    CEGUI::Direct3D9Renderer &myRenderer = CEGUI::Direct3D9Renderer::bootstrapSystem((LPDIRECT3DDEVICE9)device);

So, the question is, is there a more elegant way, currently, to achieve this? If not, can I commit these changes, or is there anything more acceptable that I could do to achieve the same effect?

I know the proper answer would be to write a Panda3D CEGUI renderer but that’s a lot of work atm and I’d rather live with the Panda GUI than waste time with that. Also, it’s very possible that a proper Panda3DCEGUI renderer wouldn’t perform so well as the pure direct3d rendering I got now.

You didn’t need to add this callback functionality - Panda3D already has callback functionality, which can be used for this purpose.

Something like this:

class CeguiCallback : public CallbackObject {
public:
  ALLOC_DELETED_CHAIN(CeguiCallback);
  virtual void do_callback(CallbackData *cbdata);
};

void CeguiCallback::do_callback(CallbackData *cbdata) {
  CEGUI::System::getSingleton().renderGUI();
  cbdata->upcall();
}

And then place this node in the scene graph, where the GUI must be rendered:

PT(CallbackNode) cb_node = new CallbackNode("CEGUI");
cb_node->set_draw_callback(new CeguiCallback);
window->get_render_2d().attach_new_node(cb_node);

To pass keyboard and mouse events to CEGUI, I suggest making a DataNode derivative and putting it in the Scene Graph under a MouseWatcher. Nik’s CEGUI implementation does this, too— I suggest looking at that one.

Yeah, that works with opengl but not with directx.

  1. If I do it that way, although it works at first, when I resize the window I get a crash. This is because the CEGUI D3D render needs to be told when we are gonna reset the device, so at least I would need the pre_reset and post_reset callbacks.

  2. I still need the get_device_int() function because I need to tell CEGUI the directx device. You don’t have that problem in opengl because there’s no such concept.

  1. Apart from the other two things, it doesn’t even work with a static window, I noticed that if I use the CallbackNode class the CEGUI elements show glitches if I change the nodes of the scene for some reason. For example hiding a node will make half of the area of a window disappear. With my callbacks this doesn’t happen. So yeah that doesn’t solve any of my 4 problems.

Edit: If you are wondering why this doesn’t work in DirectX, CrazyEddie says:

So I could save a D3D state block and restore it afterwards in my callback but that means my program needs to be aware of DirectX, right now with my solution I don’t include the directx headers anywhere in my program and that’s kind of neat. I do this thing in the Panda side:

void DXGraphicsStateGuardian9::
end_frame(Thread *current_thread) {

  if (_render_callback){
    IDirect3DStateBlock9 *state_block = NULL;
    _d3d_device->CreateStateBlock(D3DSBT_ALL, &state_block);
    state_block->Capture();
    _render_callback();
    state_block->Apply();
    state_block->Release();
  }

  HRESULT hr = _d3d_device->EndScene();

Otherwise some untextured nodes turn white. This doesn’t seem to affect performance much (my app is still at the vsync limit so I don’t know how much of a hit it is).

Not to mention that maybe it wouldn’t work even with the restoration of the block inside my callback at the CallbackNode because most probably CEGUI really needs to do the drawing right before the call to EndScene (the documentation and experience suggests this, the crashes due to non-restoration of the state look different than the crashes when I use the callbackNode), and the callback node solution doesn’t guarantee this.

Comment on this please? The CallbackNode thing doesn’t and won’t ever work for CEGUI in DirectX, so I need these changes (which I have done, I’d only need to commit) or something equivalent. Suggestions?

Did you try using DisplayRegion::set_draw_callback()? But it sounds like that’s still not high-level enough for you. I have no objection with adding GSG::set_draw_callback()/set_pre_reset_callback()/set_post_reset_callback() methods, but I’d rather you implemented them with a CallbackObject pointer, rather than a raw C++ method pointer.

GraphicsStateGuardian::get_device_int() doesn’t really sound like a great place to put this. First, casting a pointer to an integer is almost always a terrible idea. You could use a void * to avoid that. But beyond that, the idea of a “device” in this context is unique to DirectX, so I don’t think this method belongs in the base class. Instead, you can add it to DXGraphicsStateGuardian9 only, and require the caller to downcast to the appropriate class before making the call. And this will have the added benefit that you can actually make it return an LPDIRECT3DDEVICE9 type, completely avoiding the issue with typecasting.

David

That doesn’t work. The window isn’t even showing and I get broken textures in the existing models. It also doesn’t address what the post_reset() and pre_reset() solve (avoiding a crash while resizing).

Yes, I planned to do it that way, I was just testing to see if Cegui worked first.

I agree, I was just trying to be consistent with Panda:

HWND myHwnd = (HWND)window->get_graphics_window()->get_window_handle()->get_int_handle();

So I’m assuming that you didn’t use void* there so that python users could use the handle, so I was doing the same. So should I just forget about python usage here? I know python users can’t use CEGUI in this way but they may have other uses for this feature.

EDIT: Ah wait, I guess the HWND case is different cause it’s a handle instead of a pointer, is that the logic here?

Yeah, in fact I initially I did it like this then I realized I needed the d3d headers for that in my client code, so I thought I could avoid making my client code aware of DirectX by putting it in the base class. But now I have the headers in my program anyway because of the CEGUI d3d renderer so I will do that.

Yeah, HWND’s are often passed around between third-party libraries as integers, so there’s an already-established convention there. But even still, to be safe, we use size_t instead of int; and then there’s also the interface via get_os_handle() for C++ users, which returns an actual HWND, no unsafe casting required.

David

Yes, it’s as I thought initially. By putting it in the base class I could get away without including dxGraphicsStateGuardian9.h in my app. This was neat because when I include dxGraphicsStateGuardian9.h I need to put directx in the path of my app . (Then everything breaks because of the Configure macro that both DX and dconfig.h declare, it’s not the first time I have to fight this, last time I got away with undefining it inside the dx gsg, can we rename it globally for good?) , not to mention, will I need to link my app against the DX libs again?

The thing with the base gsg class design is that I could define a LPDIRECT3DDEVICE9 with just including <d3d9.h>, which is part of the Windows Platform headers, which means I need no extra linkages or paths in my project.

But by including dxGraphicsStateGuardian9.h I will be including <d3dx9.h> which is the whole directx. This means I will need to make both my app and panda aware of directx and both will be linked to it. Ugh! I’d gladly do this if it was necessary, but why do it when it isn’t.

So can we get back to the base class design? I don’t think it’s that ugly, a void* get_device would return NULL when called in opengl and tiny and the d3d device itself in directx. Also pre_reset, post_reset and draw callbacks make sense in opengl too.

EDIT: Well actually a pre_reset doesn’t make sense I guess but a pre_resize would.

All right. But I’m not sure that I like the method name “get_device()”, since the concept “device” means something different in OpenGL. I guess you could call it get_d3d_device(), as silly as that is, but that is what it’s doing, after all.

David