Well there are some instances that require you to lock the surface. I'm not sure where or why you are saying this is slow. If you only do it once per frame then its not that bad.
Here is why your code is so slow, or at least a couple of reasons I can see right off hand.
//valuse are valid now
Code:
int mempitch = (int)(ddsd.lPitch >> 2);
UINT *video_buffer = (UINT *)ddsd.lpSurface;
if(ddsd.lPitch == 800) //linear memory
{
memset(video_buffer,0,800*600);
}
else //BOOO! non-linear memory
{
UINT *dest_ptr = video_buffer;
for(int i=0;i<600;i++)
{
//clear nxt line
memset(dest_ptr,0,600);
//advance ptr
dest_ptr += (UNIT)mempitch;
}
}
Since BufferPitch according to the SDK is guaranteed to work in all cases then you need not check for linearity of the memory. This added if on every frame adds more cycles than you might think. Also you are clearing out one line at a time instead of the whole screen at once.
There are several options here. In my experience I've found that nearly every buffer has a linear pitch to it. So basically this can be broken down into a simple bitblt. Also, memset only works with bytes at a time. So in 32-bit color it must use stosb 4 times as opposed to one stosd. A stosb writes the byte in AL to ES:EDI. A stosd writes the DWORD or 32-bit value in EAX to ES:EDI. A stosd is just about 4 times faster than using stosb.
Code:
UINT *dest_ptr = video_buffer;
for(int i=0;i<600;i++)
{
//clear nxt line
memset(dest_ptr,0,600);
//advance ptr
dest_ptr += (UNIT)mempitch;
}
}
For linear memory:
Code:
UINT *dest_ptr = video_buffer;
DWORD numDwords=width*height;
asm {
mov edi,[dest_ptr]
mov ecx,numDwords
mov eax,00000h
rep stosd
}
For non-linear:
Code:
UINT *dest_ptr=video_buffer;
DWORD numDwords=width*height*bufferpitch;
asm {
mov edi,[dest_ptr]
mov ecx,numDwords
mov eax,00000h
START:
mov [es:edi],eax ;full selector might cause problems
add edi,bufferpitch
dec ecx
LOOP START
In pure C and linear memory:
Code:
for (DWORD offset=0;offset<numDwords;offset++)
{
video_ptr[offset]=0x0000;
}
In pure C and non-linear memory:
Code:
for (DWORD offset=0;offset<numDwords;offset+=bufferpitch)
{
video_ptr[offset]=0x0000;
}
You can actually get this to go very fast. My voxel engine uses this method and locks/unlocks the buffer once per frame. Unfortunately if you want to directly access the buffer there is no other way. But the performance hit is not that bad. If it is crawling it is your code or your algorithm. I can get scrolling and voxels to run very fast (if my voxel algo were just a bit faster) even whilst locking/unlocking the back buffer.
I used assembly in this post because I'm afraid to say that when you need speed you need to use assembly. Argue with me all you want about compiler optimizations but it is all bunk. Assembly is faster hands down (if you code it right) and no amount of C code is going to beat finely crafted hand-tuned assembly code. Many many many game programmer positions still want you to know assembly - trust me they use it more than you think. It is extremely useful in Direct3D for texture to texture writes and other operations. Hardware is not always available (including vertex/pixel shaders) and so when it is not....your code should switch to an assembly function.
Just no way around it in this post. For directly accessing the buffer you will need some assembly to get it fast enough. There also are ways to split the screen into sections and render each section inside the loop. Say for instance split the screen in half and instead of writing one pixel per frame you write 2. One to the left side and one to the right side. For clearing the screen this works very well. There are about a million ways to do this so I leave that as an exercise for you to try.
Dump memset. It's not suited for this.
This setup will fix the flicker.
When you set your back buffer up there are two important things you must do. First you must use the D3DPRESENTFLAG_LOCKABLE_BACKBUFFER flag to be able to create a back buffer that can be locked.
Second you must use D3DSWAPEFFECT_COPY as the SwapEffect flag to stop the flickering.
Also you CANNOT render to the primary buffer w/o causing flickering. You should always render to the BACK BUFFER and then when D3D flips it to the front it will not flicker. At no time will your image cease to exist and therefore no flicker. This is the same exact process used in the old DOS days when you rendered to a buffer and then either bitblt'ed it to the screen or page flipped it. Do not write to the primary buffer.
Code:
...
...
_sPresentParams.BackBufferWidth = width;
_sPresentParams.BackBufferHeight = height;
_sPresentParams.BackBufferFormat = D3DFMT_A8R8G8B8;
_sPresentParams.BackBufferCount = 1;
_sPresentParams.MultiSampleType = D3DMULTISAMPLE_NONE;
_sPresentParams.MultiSampleQuality = 0;
_sPresentParams.SwapEffect = D3DSWAPEFFECT_COPY;
_sPresentParams.hDeviceWindow = hwnd;
_sPresentParams.Windowed = windowed;
_sPresentParams.EnableAutoDepthStencil = true;
_sPresentParams.AutoDepthStencilFormat = D3DFMT_D24S8;
_sPresentParams.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
_sPresentParams.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
_sPresentParams.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
hr = D3D9->CreateDevice(D3DADAPTER_DEFAULT,
DeviceType,
hwnd,
vp,
&_sPresentParams,
&Device);
Getting to the back buffer in Direct3D is a bit challenging and you have to get a hold of the swap chain to do it. Here is a class called CBackBuffer that I created that works fine.
CBackBuffer.h
Code:
#ifndef BACKBUFFER
#define BACKBUFFER
#include <d3dx9.h>
#include "D3DError.h"
class CBackBuffer
{
protected:
IDirect3DDevice9 *Device;
IDirect3DSwapChain9 *SwapChain;
IDirect3DSurface9 *Surface;
D3DSURFACE_DESC BufferDesc;
D3DLOCKED_RECT BLRect;
void *Buffer;
int MemPitch;
public:
CBackBuffer(IDirect3DDevice9 *device);
int Pitch(void) {return MemPitch;};
int Width(void) {return BufferDesc.Width;};
int Height(void) {return BufferDesc.Height;};
void *Lock(void);
HRESULT Unlock();
};
#endif
CBackBuffer.cpp
Code:
#include "CBackBuffer.h"
#include <stdio.h>
CBackBuffer::CBackBuffer(IDirect3DDevice9 *device)
{
Device=device;
Device->GetSwapChain(0,&SwapChain);
SwapChain->GetBackBuffer(D3DADAPTER_DEFAULT,D3DBACKBUFFER_TYPE_MONO,&Surface);
if (!Surface) ::MessageBox(0,"GetBackBuffer Failed",0,0);
if FAILED(Surface->GetDesc(&BufferDesc)) ::MessageBox(0,"GetDesc Failed",0,0);
}
void *CBackBuffer::Lock(void)
{
D3DLOCKED_RECT lrect;
HRESULT hr;
hr=Surface->LockRect(&lrect,0,0);
if FAILED(hr)
{
char *text=D3DError::GetTextFromD3DError(hr);
::MessageBox(0,text,0,0);
return NULL;
}
MemPitch=lrect.Pitch>>2;
return lrect.pBits;
}
HRESULT CBackBuffer::Unlock(void)
{
HRESULT hr=Surface->UnlockRect();
return hr;
}
Don't worry about D3DError.h It was something I wrote to turn D3D error codes into textual error messages.....until wonderful Mr. Wizard pointed out to me that D3D already has this functionality. So I wrote all the code for nothing....but hey it was fun.
If you have any questions or this does not work I will give you my full Direct3D framework class so that you can at least get an environment up and running that takes care of all the nitty gritty stuff for you.
Also dump the DirectX 7 interfaces. It is quite possible that your video driver will fail on some of the calls. Yes they are still valid since MS DX is backwards compatible...but the driver may not utilize them in the best way anymore since they are deprecated calls. This code should bump you up to DirectX 9.0. All of the code is my own and it comes from wanting to do exactly what you are talking about - direct video access in DirectX 9.0(a,b and c) just like in the old DOS days.