RTTtexture->lock() FIX (D3D8 & D3D9)

You discovered a bug in the engine, and you are sure that it is not a problem of your code? Just post it in here. Please read the bug posting guidelines first.
Post Reply
turboferret
Posts: 49
Joined: Thu Aug 12, 2004 12:42 pm
Location: Sweden
Contact:

RTTtexture->lock() FIX (D3D8 & D3D9)

Post by turboferret »

Hi everyone!
This is how to modify the engine (0.14.0) so that you can use ITexture::lock() on textures created with IVideoDriver::createRenderTargetTexture().

Direct3D9
CD3D9Texture.h <-- URL to updated file

In the class definition, add a protected IDirect3DSurface9 member

Code: Select all

	IImage* Image;
	IDirect3DDevice9* Device;
	IDirect3DTexture9* Texture;
	IDirect3DSurface9* RTTSurface;
	core::dimension2d<s32> TextureSize;
	core::dimension2d<s32> ImageSize;
	s32 Pitch;
	ECOLOR_FORMAT ColorFormat;
CD3D9Texture.cpp <-- URL to updated file

In both constructors add

Code: Select all

RTTSurface = NULL;
Should look something like

Code: Select all

//! rendertarget constructor
CD3D9Texture::CD3D9Texture(IDirect3DDevice9* device, core::dimension2d<s32> size)
: Image(0), Device(device), TextureSize(size), 
	Texture(0), Pitch(0), ImageSize(size), HasMipMaps(0), HardwareMipMaps(0),
	IsRenderTarget(true)
{
	RTTSurface = NULL;
	#ifdef _DEBUG
    setDebugName("CD3D9Texture");
	#endif

	if (Device)
		Device->AddRef();

	createRenderTarget();
}


//! constructor
CD3D9Texture::CD3D9Texture(IImage* image, IDirect3DDevice9* device,
								   u32 flags)
: Image(image), Device(device), TextureSize(0,0), 
Texture(0), Pitch(0), ImageSize(0,0), HasMipMaps(false), HardwareMipMaps(false),
IsRenderTarget(false)
{
	RTTSurface = NULL;
	#ifdef _DEBUG
    setDebugName("CD3D9Texture");
	#endif

...
Change the destructor to

Code: Select all

//! destructor
CD3D9Texture::~CD3D9Texture()
{
	if (Device)
		Device->Release();

	if (Image)
		Image->drop();

	if (Texture)
		Texture->Release();

	if (RTTSurface)
		RTTSurface->Release();
}
change the lock() function to the following

Code: Select all

//! lock function
void* CD3D9Texture::lock()
{
	if (!Texture)
		return 0;

	HRESULT hr;
	D3DLOCKED_RECT rect;
	if(!IsRenderTarget)
	{
		hr = Texture->LockRect(0, &rect, 0, 0);
	}
	else
	{
		D3DSURFACE_DESC desc;
		Texture->GetLevelDesc(0, &desc);
		if (RTTSurface == NULL)
		{
			hr = Device->CreateOffscreenPlainSurface(desc.Width, desc.Height, desc.Format, D3DPOOL_SYSTEMMEM, &RTTSurface, NULL);
			if (FAILED(hr))
			{
				os::Printer::log("Could not lock DIRECT3D9 Texture.", ELL_ERROR);
				return 0;
			}
		}

		IDirect3DSurface9 *surface = NULL;
		hr = Texture->GetSurfaceLevel(0, &surface);
		if (FAILED(hr))
		{
			os::Printer::log("Could not lock DIRECT3D9 Texture.", ELL_ERROR);
			return 0;
		}
		hr = Device->GetRenderTargetData(surface, RTTSurface);
		if(FAILED(hr))
		{
			os::Printer::log("Could not lock DIRECT3D9 Texture.", ELL_ERROR);
			return 0;
		}
		hr = RTTSurface->LockRect(&rect, NULL, 0);
		if(FAILED(hr))
		{
			os::Printer::log("Could not lock DIRECT3D9 Texture.", ELL_ERROR);
			return 0;
		}
        return rect.pBits;
	}
	if (FAILED(hr))
	{
		os::Printer::log("Could not lock DIRECT3D9 Texture.", ELL_ERROR);
		return 0;
	}

	return rect.pBits; 
}
Change the unlock() method to look like this

Code: Select all

//! unlock function
void CD3D9Texture::unlock()
{
	if (!Texture)
		return;

	if (!IsRenderTarget)
		Texture->UnlockRect(0);
	else if (RTTSurface)
		RTTSurface->UnlockRect();
                return NULL;
}


Direct3D8
Updating the Direct3D8 texture is the same but some functions are different and of course you need to use an 8 instead of 9 in interfaces.

CD3D8Texture.h <-- URL to updated file

In the class definition, add a protected IDirect3DSurface8 member

Code: Select all

	IImage* Image;
	IDirect3DDevice8* Device;
	IDirect3DTexture8* Texture;
	IDirect3DSurface8* RTTSurface;
	core::dimension2d<s32> TextureSize;
	core::dimension2d<s32> ImageSize;
	s32 Pitch;
	ECOLOR_FORMAT ColorFormat;
CD3D8Texture.cpp <-- URL to updated file

In both constructors add

Code: Select all

RTTSurface = NULL;
should look something like

Code: Select all

//! rendertarget constructor
CD3D8Texture::CD3D8Texture(IDirect3DDevice8* device, core::dimension2d<s32> size)
: Image(0), Device(device), TextureSize(size), 
	Texture(0), Pitch(0), ImageSize(size), HasMipMaps(0), 
	IsRenderTarget(true)
{
	RTTSurface = NULL;
	#ifdef _DEBUG
    setDebugName("CD3D8Texture");
	#endif

	if (Device)
		Device->AddRef();

	createRenderTarget();
}


//! constructor
CD3D8Texture::CD3D8Texture(IImage* image, IDirect3DDevice8* device,
								   u32 flags)
: Image(image), Device(device), TextureSize(0,0), 
Texture(0), Pitch(0), ImageSize(0,0), HasMipMaps(false), IsRenderTarget(false)
{
	RTTSurface = NULL;
	#ifdef _DEBUG
    setDebugName("CD3D8Texture");
	#endif

...
Change the destructor to

Code: Select all

//! destructor
CD3D8Texture::~CD3D8Texture()
{
	if (Device)
		Device->Release();

	if (Image)
		Image->drop();

	if (Texture)
		Texture->Release();

	if (RTTSurface)
		RTTSurface->Release();
}
change the lock() function to the following

Code: Select all

//! lock function
void* CD3D8Texture::lock()
{
	if (!Texture)
		return 0;

	HRESULT hr;
	D3DLOCKED_RECT rect;
	if(!IsRenderTarget)
	{
		hr = Texture->LockRect(0, &rect, 0, 0);
	}
	else
	{
		D3DSURFACE_DESC desc;
		Texture->GetLevelDesc(0, &desc);
		if (RTTSurface == NULL)
		{
			hr = Device->CreateImageSurface(desc.Width, desc.Height, desc.Format, &RTTSurface);
			if (FAILED(hr))
			{
				os::Printer::log("Could not lock DIRECT3D8 Texture.", ELL_ERROR);
				return 0;
			}
		}

		IDirect3DSurface8 *surface = NULL;
		hr = Texture->GetSurfaceLevel(0, &surface);
		if (FAILED(hr))
		{
			os::Printer::log("Could not lock DIRECT3D8 Texture.", ELL_ERROR);
			return 0;
		}
		hr = Device->CopyRects(surface, NULL, 0, RTTSurface, NULL);
		if(FAILED(hr))
		{
			os::Printer::log("Could not lock DIRECT3D8 Texture.", ELL_ERROR);
			return 0;
		}
		hr = RTTSurface->LockRect(&rect, NULL, 0);
		if(FAILED(hr))
		{
			os::Printer::log("Could not lock DIRECT3D8 Texture.", ELL_ERROR);
			return 0;
		}
        return rect.pBits;
	}
	if (FAILED(hr))
	{
		os::Printer::log("Could not lock DIRECT3D8 Texture.", ELL_ERROR);
		return 0;
	}

	return rect.pBits; 
}
Change the unlock() method to look like this

Code: Select all

//! unlock function
void CD3D8Texture::unlock()
{
	if (!Texture)
		return;

	if (!IsRenderTarget)
		Texture->UnlockRect(0);
	else if (RTTSurface)
		RTTSurface->UnlockRect();
                return NULL;
}


// cheers and good luck! :D
This monkey is useless, it only has ONE ass!!!
alc
Posts: 31
Joined: Sun Jun 25, 2006 10:59 pm
Location: Denmark

Post by alc »

I hate to bring up an older post, but I have just tried implementing this fix to engine v. 1.0, and I do seem to have a problem. Well, two, in fact.

1) When using getPitch() on a texture made with createRenderTargetTexture, I always get zero. I believe this can be solved by changing

Code: Select all

switch(d3DFormat)
{
case D3DFMT_X1R5G5B5:
case D3DFMT_A1R5G5B5:
	ColorFormat = ECF_A1R5G5B5;
	break;
case D3DFMT_A8B8G8R8:
case D3DFMT_A8R8G8B8:
case D3DFMT_X8R8G8B8:
	ColorFormat = ECF_A8R8G8B8;
	break;
case D3DFMT_R5G6B5:
	ColorFormat = ECF_R5G6B5;
	break;
default:
	ColorFormat = (ECOLOR_FORMAT)-1;
};
to

Code: Select all

switch(d3DFormat)
{
case D3DFMT_X1R5G5B5:
case D3DFMT_A1R5G5B5:
	ColorFormat = ECF_A1R5G5B5;
	Pitch = TextureSize.Width * 2;  // alc
	break;
case D3DFMT_A8B8G8R8:
case D3DFMT_A8R8G8B8:
case D3DFMT_X8R8G8B8:
	ColorFormat = ECF_A8R8G8B8;
	Pitch = TextureSize.Width * 4;  // alc
	break;
case D3DFMT_R5G6B5:
	ColorFormat = ECF_R5G6B5;
	Pitch = TextureSize.Width * 2;  // alc
	break;
default:
	ColorFormat = (ECOLOR_FORMAT)-1;
};
in createRenderTarget function in CD3D9Texture.cpp. Would you agree? Or is this the wrong place to do it? It works fine, but might be inappropriate.

2) Secondly, and much worse, when I use lock() to get a pointer to the pixels, I do get a pointer, that gives me access to the pixel values. However, when trying to change them, I do not see the change in the texture. But if I check the pixel values after assigning new values I do see the change. Thus, I think that the pointer given by lock() in this situation is to a copy of the texture?!

If I do the same to a texture given by getTexture it works fine, and I can see the changed texture on the screen.

Looking through CD3D9Texture.cpp I realize that these two methods are rather different. Still, I would be nice to be able to actually change the pixels, rather than just read them!

Any comments on this; or even better, a solution?
alc
Posts: 31
Joined: Sun Jun 25, 2006 10:59 pm
Location: Denmark

Post by alc »

I just realized: A possible workaround is to create an empty standard (non-rendered) texture, and copy the rendered texture to this. Then it is possible to edit it and display the altered version on the screen.
turboferret
Posts: 49
Joined: Thu Aug 12, 2004 12:42 pm
Location: Sweden
Contact:

Post by turboferret »

Hi alc!
I'm not sure about getPitch, sounds like it would fit there.
You can't modify the pixels of a rendertargettexture. It is in fact a copy of the texture like you suggest. I don't think there's an easy fix for this, since a rendertarget is pretty different from a regular texture.

Thanks for the interest!
This monkey is useless, it only has ONE ass!!!
Post Reply