Sign in to follow this  
Khatharr

D3D render font to existing surface?

Recommended Posts

So I've got this engine I'm building and it has a 'Bitmap' class that handles loading, storing and manipulating textures. One of the things I want to be able to do is draw text onto the texture surface. This is for a 2D engine, so I'm not using mips or anything funky. I've tried everything I can think of to get this to work but I can't seem to figure out what the problem is (apart from using a M$ api).

Here's a rar containing a cpp file and a png file to demonstrate the problem. This compiles for me in VS 2008 without any problems:

[url="http://mayobe.webs.com/font_to_tex_fail.rar"]http://mayobe.webs.c...to_tex_fail.rar[/url]

If I load the texture from a file then I can't draw text on it, but if I create an empty texture I can.
Here's the code that's in the cpp file in case the problem is obvious to more experienced DX devs:

[code]//////////headers and libs
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")
/////vert structure used by directx
struct CUSTOMVERTEX {
FLOAT x, y, z, rhw;
FLOAT u, v;
};
/////globals
CUSTOMVERTEX verts[4];
LPDIRECT3DVERTEXBUFFER9 v_buffer;
LPDIRECT3D9 d3d;
LPDIRECT3DDEVICE9 d3ddev;
HINSTANCE hInstance;
HWND hWnd;
MSG msg;
//////protos
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void create_registered_windowclass(LPCWSTR classname);
void setup_window(int nCmdShow);
int update_win();
void setup_d3d();
void setup_verts();
////////////////////////////////////////MAIN////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hin, HINSTANCE prev, LPSTR cmln, int cmdsh) {
hInstance = hin;
setup_window(cmdsh);
setup_d3d();

//make the texture object
DWORD width = 200;
DWORD height = 100;
LPDIRECT3DTEXTURE9 ppTexture;
ppTexture = NULL;
//#define FROM_FILE // ~TOGGLE THIS TO SEE THE PROBLEM~
#ifdef FROM_FILE
//this does not work correctly. the text is not visible
D3DXCreateTextureFromFileEx(d3ddev, L"test.png", 0, 0, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, D3DX_FILTER_POINT, D3DX_DEFAULT, 0, 0, 0, &ppTexture);
#else
//this works correctly. the text is visible
D3DXCreateTexture(d3ddev, width, height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &ppTexture);
#endif

//get a reference to the texture's surface
LPDIRECT3DSURFACE9 ppSurface = NULL;
ppTexture->GetSurfaceLevel(0, &ppSurface);

//set up render to surface object
LPD3DXRENDERTOSURFACE ppRTS = NULL;
D3DXCreateRenderToSurface(d3ddev, width, height, D3DFMT_A8R8G8B8, FALSE, D3DFMT_UNKNOWN, &ppRTS);

//set up the font object
LPD3DXFONT ppFont = NULL;
D3DXCreateFont(d3ddev, 22, 0, FW_NORMAL, 1, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, L"Tahoma", &ppFont);

//draw text on the surface
D3DVIEWPORT9 vp = {0, 0, width, height, 0.5f, 0.5f};
ppRTS->BeginScene(ppSurface, &vp);
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
RECT rect = {10, 10, 620, 460};
ppFont->DrawText(NULL, L"Test...", -1, &rect, DT_NOCLIP, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f));
ppRTS->EndScene(D3DX_FILTER_POINT);

setup_verts();
while((update_win()) == 0) {
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(128, 128, 128), 1.0f, 0);
d3ddev->BeginScene();
d3ddev->SetFVF(D3DFVF_XYZRHW|D3DFVF_TEX1);
d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX));
d3ddev->SetTexture(0, ppTexture);
d3ddev->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////

//////////Window management crap
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if(message == WM_DESTROY) {PostQuitMessage(0);}
return (DefWindowProc(hWnd, message, wParam, lParam));
}

void create_registered_windowclass(LPCWSTR classname) {
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = classname;
RegisterClassEx(&wc);
}

void setup_window(int nCmdShow) {
create_registered_windowclass(L"winclass");
hWnd = CreateWindowEx(NULL, L"winclass", L"test",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
640, 480, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
}

int update_win() {
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if(msg.message == WM_QUIT) {return 1;}
}
return 0;
}
//////////End of Window management crap

//////////D3D crap
void setup_d3d() {
d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferWidth = 640;
d3dpp.BackBufferHeight = 480;
d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev);
}

void setup_verts() {
//prep the vert data
ZeroMemory(&verts, sizeof(verts));
verts[1].u = verts[2].u = verts[2].v = verts[3].v = 1;
for(int i = 0; i < 4; ++i) {
verts[i].x = 220 + (verts[i].u * 200);
verts[i].y = 190 + (verts[i].v * 100);
verts[i].z = verts[i].rhw = 1;
}
//load the buffer
d3ddev->CreateVertexBuffer(sizeof(verts), 0, D3DFVF_XYZRHW|D3DFVF_TEX1, D3DPOOL_MANAGED, &v_buffer, NULL);
void* pvoid;
v_buffer->Lock(0, 0, &pvoid, 0);
memcpy(pvoid, verts, sizeof(verts));
v_buffer->Unlock();
}
////////////////////End of D3D crap
[/code]

Any advice would be greatly appreciated. I'm not necessarily married to this method either. The Bitmap class is very simple at this point so I can implement pretty much whatever I need to.

OH! Also, if I use ID3DXSprite should I offset my RHW translations by -0.5 or does it not matter with that method?

Share this post


Link to post
Share on other sites
Well, I've got it working. I'm doing what was suggested and it works alright when I plug it into my actual program.

I can't figure out what the difference between the two textures is. A texture created blank can be rendered to repeatedly with no problems but a texture created by loading a bitmap can't be rendered to at all. I've poked around the properties of both textures and both surfaces but I can't see anything that would cause this behavior. In my program what I'm doing is creating the texture blank and then loading the bitmap file into a temporary texture which is used to render the bitmap contents to the 'real' texture, then discarded. While this is functional it's also a nice chunk of gibberish that had to be added to my texture handling class and I fear that it may lead to slowdown in cases where many textures need to be loaded sequentially.

If anyone knows what it is that causes RTS to fail (it's giving 'invalid call' from the RTS BeginScene()) with textures created from files I'd really like to know.

(Honestly, I've never encountered such a convoluted API...)

Thank you again for your advice, iedoc. :)

Here's the source of a modified test app showing how I'm getting it to work:

[code]//Please forgive the mess.
//It's done this way to isolate the relevant code in WinMain() without the
//distraction of all the inits and declarations, etc.
//You should be able to read WinMain() and pretty much see the whole story
//without having to worry about the rest.

#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

struct CUSTOMVERTEX {
FLOAT x, y, z, rhw;
FLOAT u, v;
};

CUSTOMVERTEX verts[4];
LPDIRECT3DVERTEXBUFFER9 v_buffer, v_buffer_inner;
LPDIRECT3D9 d3d;
LPDIRECT3DDEVICE9 d3ddev;
HINSTANCE hInstance;
HWND hWnd;
MSG msg;
DWORD width = 200, height = 100; //dims of texture I'm using, rather than grabbing them from functions
LPD3DXRENDERTOSURFACE ppRTS = NULL;
LPD3DXFONT ppFont = NULL;
RECT rect = {10, 10, 620, 460};
int update_win();
void run_inits(HINSTANCE, int);


int WINAPI WinMain(HINSTANCE hin, HINSTANCE prev, LPSTR cmln, int cmdsh) {
run_inits(hin, cmdsh); //runs all initializations (OS and D3D)

//this texture loads the bitmap data but for some reason can't be rendered to
LPDIRECT3DTEXTURE9 ppImgTexture = NULL;
D3DXCreateTextureFromFileEx(d3ddev, L"test.png", 0, 0, 1, 0,
D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, D3DX_FILTER_POINT,
D3DX_DEFAULT, 0, 0, 0, &ppImgTexture);

//this texture has the bitmap data rendered to it and then is used as the 'real' texture
LPDIRECT3DTEXTURE9 ppTexture = NULL;
D3DXCreateTexture(d3ddev, width, height, 1, 0,
D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &ppTexture);

//prepare for RTS
D3DVIEWPORT9 vp = {0, 0, width, height, 0.5f, 0.5f};
LPDIRECT3DSURFACE9 ppSurface = NULL;
ppTexture->GetSurfaceLevel(0, &ppSurface);

//RTS - draw bitmap data to texture then draw font string to texture
ppRTS->BeginScene(ppSurface, &vp);
d3ddev->SetFVF(D3DFVF_XYZRHW|D3DFVF_TEX1);
d3ddev->SetStreamSource(0, v_buffer_inner, 0, sizeof(CUSTOMVERTEX));
d3ddev->SetTexture(0, ppImgTexture);
d3ddev->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);
ppFont->DrawText(NULL, L"Test...", -1, &rect, DT_NOCLIP,
D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f));
ppRTS->EndScene(D3DX_FILTER_POINT);

//draw texture to screen (we overwrite this frame immediately, but I've seen it and it renders correctly)
d3ddev->BeginScene();
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(128, 128, 128), 1.0f, 0);
d3ddev->SetFVF(D3DFVF_XYZRHW|D3DFVF_TEX1);
d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX));
d3ddev->SetTexture(0, ppTexture);
d3ddev->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);

//prepare for second RTS
rect.top += 42; //nudge our text drawing rect down a bit
ppTexture->GetSurfaceLevel(0, &ppSurface); //dunno if this can change so I'm playing it safe

//RTS
ppRTS->BeginScene(ppSurface, &vp);
ppFont->DrawText(NULL, L"Antelopes!", -1, &rect, DT_NOCLIP,
D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f));
ppRTS->EndScene(D3DX_FILTER_POINT);

//draw texture to screen again
d3ddev->BeginScene();
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(128, 128, 128), 1.0f, 0);
d3ddev->SetFVF(D3DFVF_XYZRHW|D3DFVF_TEX1);
d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX));
d3ddev->SetTexture(0, ppTexture);
d3ddev->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);

//stall until the user closes the window - update_win() is just the message handling loop
int sult = 0;
while(sult == 0) {sult = update_win();}
return 0;
}

//OS management
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if(message == WM_DESTROY) {PostQuitMessage(0);}
return (DefWindowProc(hWnd, message, wParam, lParam));
}

void create_registered_windowclass(LPCWSTR classname) {
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = classname;
RegisterClassEx(&wc);
}

void setup_window(int nCmdShow) {
create_registered_windowclass(L"winclass");
hWnd = CreateWindowEx(NULL, L"winclass", L"test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
}

int update_win() {
//message dispatcher
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if(msg.message == WM_QUIT) {return 1;}
}
return 0;
}

//D3D inits
void setup_d3d() {
//prep d3d environment
d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferWidth = 640;
d3dpp.BackBufferHeight = 480;
d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &d3ddev);
}

void setup_verts() {
//This just sets up the vertex array for the first RTS, saves
//it to a vertex buffer, then re-uses the same vertex array
//(with minor adjustments) to set the vertex buffer for screen-drawing
ZeroMemory(&verts, sizeof(verts));
verts[1].u = verts[2].u = 1;
verts[2].v = verts[3].v = 1;
for(int i = 0; i < 4; ++i) {
verts[i].x = (verts[i].u * 200);
verts[i].y = (verts[i].v * 100);
verts[i].z = 0; verts[i].rhw = 1;
}
void* pvoid;
d3ddev->CreateVertexBuffer(sizeof(verts), 0, D3DFVF_XYZRHW|D3DFVF_TEX1, D3DPOOL_MANAGED, &v_buffer_inner, NULL);
d3ddev->CreateVertexBuffer(sizeof(verts), 0, D3DFVF_XYZRHW|D3DFVF_TEX1, D3DPOOL_MANAGED, &v_buffer, NULL);
v_buffer_inner->Lock(0, 0, &pvoid, 0);
memcpy(pvoid, verts, sizeof(verts));
v_buffer_inner->Unlock();
for(int i = 0; i < 4; ++i) {verts[i].x += 220; verts[i].y += 190;}
v_buffer->Lock(0, 0, &pvoid, 0);
memcpy(pvoid, verts, sizeof(verts));
v_buffer->Unlock();
}

//Derp
void run_inits(HINSTANCE hin, int cmdsh) {
hInstance = hin;
setup_window(cmdsh);
setup_d3d();
setup_verts();
D3DXCreateRenderToSurface(d3ddev, width, height, D3DFMT_A8R8G8B8, FALSE, D3DFMT_UNKNOWN, &ppRTS);
D3DXCreateFont(d3ddev, 22, 0, FW_NORMAL, 1, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, L"Tahoma", &ppFont);
}
[/code]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this