I'm working on a university project: 3d editor which allows edition of heightmaps. One of the tough decisions I had to face was choosing GUI tookit. After some considerations I decided to try wxWidgets :] and immediately started looking on the internet for ways of embedding Direct3D 9 context inside wxWidgets application, but to my disbelief I've found no ready-to-go samples!
Geez, one would think that such a popular and mature library like wxWidgets would have tons of samples on mixing it with currently most popular graphics API, unfortunately it's not true and I had to combine various pieces of code together to make it all work. It was painfull but eventually I made it: simple windowed application that can be resized, which embeds D3D context on which color triangle is rendered; there's also a button which changes color of the background behind triangle. In order to simplify the life of other programmers, I release it as public domain and you can see it here:
Code: Select all
/*
Author: Koshmaar (www.koshmaar.pl)
License: public domain
Description: shows how to integrate Direct3D9 with wxWidgets in one application - I was looking for a sample
all over the internet but I've found none, so I decided to make my own and share it with the world.
It's based on http://forums.wxwidgets.org/viewtopic.php?t=20948 and http://wiki.wxwidgets.org/Making_a_render_loop
*/
#include "wx/sizer.h"
#include "wx/wx.h"
#define D3D_DEBUG_INFO
#include <d3d9.h>
#include <d3dx9.h>
IDirect3DDevice9 * Device;
#pragma comment (lib, "dxguid.lib")
// *************************** DirectX ************************
struct MY_TRANSFORMED_VERTEX
{
FLOAT x, y, z, rhw;
DWORD color;
};
#define D3DFVF_MY_TRANSFORMED_VERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
MY_TRANSFORMED_VERTEX triangle2d[] =
{
{ 0.0f, 0.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color
{ 570.0f, 395.0f, 0.5f, 1.0f, 0xff00ff00, },
{ 100.0f,295.0f, 0.5f, 1.0f, 0xff00ffff, },
};
//--------------------------------------------------------------------
class MyFrame;
class DirectXPanel : public wxPanel
{
public:
DirectXPanel(wxFrame* parent) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(640, 480) /*DX context size*/, wxWANTS_CHARS /* pressing arrows key events will be propagated, see: http://wxforum.shadonet.com/viewtopic.php?t=21669 */ )
{
// initialize direct3d
IDirect3D9 *D3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS present_params;
ZeroMemory( &present_params, sizeof(present_params) );
present_params.Windowed = true;
present_params.SwapEffect = D3DSWAPEFFECT_DISCARD;
present_params.BackBufferFormat = D3DFMT_UNKNOWN;
present_params.BackBufferWidth = GetSize().x;
present_params.BackBufferHeight = GetSize().y;
//present_params.hDeviceWindow = (HWND)GetHandle(); // not really needed
//set the params, best to make the backbuffer the size of the users screen resolution, so account for resizeing
if ( FAILED( D3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)GetHandle()/*get handle from wxWidgets, vitally important*/, D3DCREATE_HARDWARE_VERTEXPROCESSING, &present_params, &Device) ))
{
wxMessageBox( "Failed to CreateDevice for Direct3D", "Error");
throw "aaaaaaaaaaaaaa!!!"; // I agree that this error handling scheme is not very professional
}
}
void paintEvent(wxPaintEvent& evt)
{
wxPaintDC dc(this);
render(dc);
}
void paintNow()
{
wxClientDC dc(this);
render(dc);
}
void render( wxDC& dc ); // deffered to make the definition of MyFrame available in its body
MyFrame * frame; // the same as parent in constructor, used to call its render method
DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(DirectXPanel, wxPanel)
EVT_PAINT(DirectXPanel::paintEvent)
END_EVENT_TABLE()
// ************************************************************
enum
{
wxMyButton = 10
};
class MyFrame : public wxFrame
{
IDirect3DVertexBuffer9 * vertex_buffer;
int color;
public:
MyFrame() : wxFrame((wxFrame *)NULL, -1, wxT("DX + WX integration"), wxDefaultPosition, wxSize(800, 600)), color(0) { }
void onClose(wxCloseEvent& evt);
void MyButton( wxCommandEvent & evt)
{
color += 10;
}
void CreateResources()
{
if (FAILED( Device->CreateVertexBuffer( 3*sizeof(MY_TRANSFORMED_VERTEX), 0, D3DFVF_MY_TRANSFORMED_VERTEX, D3DPOOL_MANAGED, &vertex_buffer, 0) ) )
{
wxMessageBox( "Failed to create vertex buffer", "Error");
return;
}
void * buffer;
if (FAILED( vertex_buffer->Lock(0, sizeof(triangle2d), (void**)&buffer, 0) ) )
{
wxMessageBox( "Failed to lock vertex buffer", "Error");
return;
}
memcpy( buffer, triangle2d, sizeof(triangle2d) );
vertex_buffer->Unlock();
SetBackgroundColour(wxColour(230, 230, 230));
ClearBackground();
}
void Render() // renders on DirectXPanel context, but logic moved here to make DirectXPanel just an utility class
{
Device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(color, 0, 255), 1, 0);
Device->BeginScene();
Device->SetStreamSource( 0, vertex_buffer, 0, sizeof(MY_TRANSFORMED_VERTEX) );
Device->SetFVF( D3DFVF_MY_TRANSFORMED_VERTEX );
Device->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1);
Device->EndScene();
Device->Present(0, 0, 0, 0);
}
DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_CLOSE(MyFrame::onClose)
EVT_BUTTON(wxMyButton, MyButton)
END_EVENT_TABLE()
void DirectXPanel::render( wxDC& dc )
{
if (frame)
frame->Render();
}
// ************************************************************
class MyApp: public wxApp
{
bool render_loop_on;
bool OnInit();
void onIdle(wxIdleEvent& evt);
MyFrame* frame;
DirectXPanel* drawPane;
public:
void activateRenderLoop(bool on);
};
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
render_loop_on = false;
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
frame = new MyFrame();
drawPane = new DirectXPanel( frame );
drawPane->frame = frame;
frame->CreateResources();
sizer->Add(drawPane, 0, wxSHAPED | wxALL | wxALIGN_CENTER, 15); // wxLEFT | wxRIGHT | wxCENTER | wxEXPAND | wxSHAPED
sizer->Add( new wxButton(frame, wxMyButton, "Button"), 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 10 );
frame->SetSizer(sizer);
frame->Show();
activateRenderLoop(true);
return true;
}
void MyApp::activateRenderLoop(bool on)
{
if(on && !render_loop_on)
{
Connect( wxID_ANY, wxEVT_IDLE, wxIdleEventHandler(MyApp::onIdle) );
render_loop_on = true;
}
else if(!on && render_loop_on)
{
Disconnect( wxEVT_IDLE, wxIdleEventHandler(MyApp::onIdle) );
render_loop_on = false;
}
}
void MyApp::onIdle(wxIdleEvent& evt)
{
if(render_loop_on)
{
drawPane->paintNow();
evt.RequestMore(); // render continuously, not only once on idle
}
}
void MyFrame::onClose( wxCloseEvent& evt ) // MyFrame, needs to know MyApp definition
{
wxGetApp().activateRenderLoop(false);
evt.Skip(); // don't stop event, we still want window to close
}