conv3ds -X: save templates. Nothing to worry about, part of the nature of x-files. Useful though if you want to see what the various parameters of things (like materials) mean.
conv3ds -m: merge all objects into one single mesh. Useful if you don't need separate meshes, and can speed up loading.
conv3ds -x: Save as text file: Definitely not a bad idea, especially when debugging. It helps to have a look at the files etc
conv3ds -T: Create a top-level frame with all objects as child frames. This is useful since the pFrame->Load loads only the first frame it finds in the file.
conv3ds -v Possible bug (possibly not in v5, only in v3) .. seems to need XForms reset in 3dstudio or strange things happen. Will maybe check this out.
When using a pFrame object to load a mesh from a mesh file, and you want to retain (more coming)
Coming:
Plan to learn OpenGL: Comparison etc, as unbiased as I can muster up.
Enumerating device drivers
Callbacks
RM vs IM
A decent sample with D/L'able code.
Comments on MS samples
How to compile with NMAKE
faq: main: linker error
3D math
Fog + some other frame rate optimization techniques
Loading and displaying a .bmp (or .png :)
Execute buffers vs DrawPrimitive
Microsoft did not provide a "PutPixel"-type function with DirectDraw to draw a pixel onto a DirectDraw surface.
Granted, no real game is going to draw its graphics using a PutPixel routine, since it would be too slow - but it would have been nice to
have such a "convenience function", and it could also be useful for testing out stuff. Some applications that might not
necessarily need speed could also benefit from the convenience of a PutPixel routine. But, DirectX was not designed with convenience in
mind.
Anyway, there are a number of ways to go about writing a PutPixel routine. One way would be to use GDI functions on the
surface, using GetDC(). Another way is to use the IDirectDrawSurface::Blt() function, specifying a solid color operation with a 1x1
rectangle. A third way is to obtain a pointer to the memory representing the surface by calling IDirectDrawSurface::Lock(), and drawing the
pixel in memory yourself. Each of these methods demonstrates general techniques whose usefulness goes beyond a simple PutPixel function.
The basic technique here is to obtain a pointer to the memory representing the surface. You then calculate the offset into
that memory for the coordinates of the pixel you wish to draw, and then you write the bytes representing the color of the pixel you want
into that position in memory. There is nothing complex or difficult here, it's just a lot of grunt-work and jumping through hoops.
By default, you may not just write directly into the memory of a surface. You first have to obtain exclusive access to that
surface, by "locking" it. This is done with a call to The pointer to the surface memory is one of the members of the DDSURFACEDESC structure, and is filled by the call to
Now, to draw a pixel, you need to understand how
pixels are represented in memory, which will depend on what color
depth screen mode your surface is (see Chapter 2). Of course, if you
create a "generic" PutPixel routine, that will work no matter what
mode you are in, you will probably want to specify the color in some
human-readable format, like an RGB triple. The PutPixel routine must
then convert this, store it somewhere, and dump it to the surface
memory. I use an Here is the actual PutPixel routine, taking as input
a DirectDraw surface, an (x,y) location, and an RGB triple with r,g
and b in the range 0 to 255. The function returns 0 for
success. Notice the function, like CreateRGB, assumes the existence of
g_iBpp, which I will explain in a bit.
There are a few things worth mentioning about this
function. Firstly, it can be optimized quite a lot, I know. But in a
production game, this is not the function you will be using for all
your drawing routines anyway. (One of the rules of optimization -
never optimise what you don't need to.) Secondly, it makes some crass
assumptions about your architecture - it may not work if your system
is big endian. However, the Intel x86 is little endian, and as far as
I know DirectX is only really available on the Intel x86. Thirdly, it
only knows about a limited number of screen modes, and sometime in the
future, when people start using video modes with higher bit depths
(like 48), the code may no longer work. (However, as far as I can
tell, DirectDraw itself may not be able to handle 48-bit color
depths.) Anyway, the point is that to make the above PutPixel routine
"correct" requires quite a bit more work still.
The Something else worth mentioning is that if the
DirectDraw surface here is the primary surface - that is, it
represents the entire screen, and you are in windowed mode and not in
full-screen mode - then the pixel offsets here are relative to the top
left of the screen, and not to your application window. You will find
yourself drawing all over the screen, unless you work the offset of
your window into your calculations (eg by using the ClientToScreen
Win32 call or something.)
This routine is demonstrated in the supplied
sample DDSamp.
This method also makes use of the
This method is quite simple, and probably a bit
"safer" than the method described in 5.1.1.
All I've done here is:
That's about it.
Before you go running off locking every surface you see now, it must first be mentioned that there are some limitations to
surface locking (what did you expect?) These are summarized from the DirectX help files.
Note that you can have multiple rectangles on a surface locked at the same time, so long as the rectangles don't overlap.
The DirectX documentation also makes the following warning which I don't fully understand *:
"Copy aligned to display memory. Windows 95 uses a
page fault handler, Vflatd.386, to implement a virtual flat-frame
buffer for display cards with bank-switched memory. The handler allows
these display devices to present a linear frame buffer to
DirectDraw. Copying unaligned to display memory can cause the system
to suspend operations if the copy spans memory banks."
2. Drawing a pixel with DirectDraw
2.1. PutPixel by surface locking
Lock()
.
HRESULT IDirectDrawSurface::Lock( LPRECT pRect, LPDDSURFACEDESC pDDSD, DWORD dwFlags, HANDLE hEvent );
Lock()
with a valid pointer. You may then write to this memory, and when you are finished with it you must call
Unlock
on that surface.
unsigned int
to temporarily store
the pixel value. For convenience, I have created a
CreateRGB
function that can generate this
unsigned int
from supplied RGB values which range
from 0 to 255:
/*--------------------------------------------------------------------------*/
// Create color from RGB triple
unsigned int CreateRGB( int r, int g, int b )
{
unsigned int pixel;
switch (g_iBpp)
{
case 8:
// Here you should do a palette lookup to find the closest match.
// I'm not going to bother with that. Many modern games no
// longer support 256-color modes.
break;
case 16:
// Break down r,g,b into 5-6-5 format.
pixel = ((r/8)<<11) | ((g/4)<<5) | (b/8);
break;
case 24:
case 32:
pixel = (r<<16) | (g<<8) | (b);
break;
default:
pixel =0;
}
return pixel;
}
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
int PutPixel( LPDIRECTDRAWSURFACE pDDS, int x, int y, int r, int g, int b )
{
DDSURFACEDESC ddsd; // Surface description
unsigned int pixel; // Store pixel to be dumped to screen
int offset; // Store offset of destination memory location
unsigned char * szSurface; // Store pointer to surface
HRESULT hr;
int pixelwidth; // The width of a single pixel, in bytes
// Initialize struct information
ddsd.dwSize = sizeof(ddsd);
// Calculate how the pixel is represented in memory
pixel = CreateRGB( r, g, b );
// Lock entire surface, wait if it is busy, return surface memory pointer
hr = pDDS->Lock( NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );
if (FAILED(hr))
return -1;
// Get surface memory pointer
szSurface = (unsigned char*)ddsd.lpSurface;
// Calculate the width of a pixel in bytes - this is how many bytes
// we must write to the surface
switch (g_iBpp)
{
case 16: pixelwidth = 2; break;
case 24: pixelwidth = 3; break;
case 32: pixelwidth = 4; break;
default: pixelwidth = 1;
}
// Calculate memory offset
// (Notice I use dwPitch instead of dwWidth - see Chapter 2)
offset = (y*ddsd.lPitch + x*pixelwidth);
// Copy pixel onto the surface, need string.h for this
memcpy( szSurface + offset, &pixel, pixelwidth );
// Unlock the surface. The parameter may be NULL if you locked the entire
// surface, otherwise it must be a pointer to the DirectDraw surface memory.
hr = pDDS->Unlock( NULL );
if (FAILED(hr))
return -2;
return 0;
}
/*--------------------------------------------------------------------------*/
g_iBpp
in the code above is an
integer representing the bits per pixel of the screen mode of the
surface. You can get this from the DDSURFACEDESC.
2.2. PutPixel by using IDirectDrawSurface::Blt
CreateRGB
function described in 5.1.1, so go read
that part now if you have not done so yet.
/*--------------------------------------------------------------------------*/
// PutPixel routine for a DirectDraw surface
void DDPutPixel( LPDIRECTDRAWSURFACE pDDS, int x, int y, int r, int g, int b )
{
HRESULT hr;
DDBLTFX ddbfx;
RECT rcDest;
POINT p;
// Safety net
if (g_pDDS == NULL)
return;
// Initialize the DDBLTFX structure with the pixel color
ddbfx.dwSize = sizeof( ddbfx );
ddbfx.dwFillColor = (DWORD)CreateRGB( r, g, b );
// Prepare the destination rectangle as a 1x1 (1 pixel) rectangle
p.x = x;
p.y = y;
ClientToScreen( g_hWnd, &p );
OffsetRect( &rcDest, p.x, p.y );
SetRect( &rcDest, p.x, p.y, p.x+1, p.y+1 );
// Blit 1x1 rectangle using solid color op
hr = g_pDDS->Blt( &rcDest, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &ddbfx );
if (FAILED(hr))
OutputDebugString( (LPCTSTR)"Royal fuckup\n" );
}
/*--------------------------------------------------------------------------*/
Blt
on the surface, specifying DDBLT_COLORFILL as the blit operation
3. Some words about surface locking
IDirectDrawSurface::GetDC
and IDirectDrawSurface::ReleaseDC
also implicitly call Lock and
Unlock, so use those with caution as well.
Okay, so say you have a chunk of data you want to write out and you want to
directly dump it out to the screen. The hardware you are running on is
using the page buffering method. Let's say page 0 of the video memory
(64K) is mapped to virtual address 0x95310000. Let's also say that the
software is doing bitmap sprite drawing and each sprite fits in a grid with
a cell size of 20x20 pixels. Finally, the video mode being displayed is
640x480x256 for simplicity's sake.
Drawing the individual sprites directly to memory causes the following
problems:
1) Vertical-sync refresh shearing problems (the code ignores the VSync
signal from the display controller and draws the graphic anyway...only part
of the graphic gets drawn causing weird shearing problems).
2) Poorer response due to extra page swaps and hundreds of extra page faults.
The second one is what Microsoft is referring to.
Time to do some math:
640x480x256 = 640 * 480 bytes = 307200 bytes video ram
Since only one 64K video bank can be loaded at a time:
307200 / 65536 = 4.6875 pages of video ram ~= 5 pages of video ram
Assuming the OS uses 4K pages (Windows tends to):
5 pages of video ram * 65536 / 4096 = 80 4K pages
The OS, however, only needs 16 4K pages (65536 / 4096 = 16) since the others have to be swapped into the buffer. So, the 16 pages are mapped starting at address 0x95310000 and ending on address 0x9531FFFF. The remaining 64 (80 - 16) pages are marked for page faults so that when they are referenced, the OS will swap in the appropriate pages.
Now, pretend that you are drawing sprite row 5 (zero-based). This row starts at address 0x9531FA00. Here is what happens when each row of the sprite is written:
Sprite Row 0) Writes to 0x9531FA00-0x9531FA13 okay.
Sprite Row 1) Writes to 0x9531FC80-0x9531FC93 okay.
Sprite Row 2) Writes to 0x9531FF00-0x9531FF13 okay.
Sprite Row 3) Writes to 0x95320180-0x95320193 Page Fault.
Here is where the OS pauses the process until it can get the next 64K of
memory (16 pages). The OS probably overwrites the current 64K since
allocating a new chunk would take too long. If this is the case, the OS
has to update the mapping table. Before resuming the process, the OS marks
the previous 16 pages so that they will page fault on reference (since they
are no longer in memory). Finally, the process has to be resumed and
execution returned back to the point of failure. All of this makes the video memory look like one big chunk of data to the program, but it really is not. To make this situation worse is when you move to draw the next sprite in the row:
Sprite Row 0) Writes to 0x9531FA14-0x9531FA27 Page Fault.
...
Sprite Row 3) Writes to 0x95320194-0x953201A7 Page Fault.
The OS has to continually swap video memory banks in and out...which is an extremely slow operation. For this reason, Microsoft made the wise decision to have back-buffers and let DirectX handle the issues with video memory banks.
So, as you can see, there is a reason why Microsoft put that documentation into MSDN, but you probably did not want to know how it all worked...but now you know!
Hope this helps!"