// Copyright (C) 2002-2009 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_DIRECT3D_9_ #define _IRR_DONT_DO_MEMORY_DEBUGGING_HERE #include "CD3D9Texture.h" #include "CD3D9Driver.h" #include "os.h" #include #ifndef _IRR_COMPILE_WITH_DIRECT3D_8_ // The D3DXFilterTexture function seems to get linked wrong when // compiling with both D3D8 and 9, causing it not to work in the D3D9 device. // So mipmapgeneration is replaced with my own bad generation in d3d 8 when // compiling with both D3D 8 and 9. // #define _IRR_USE_D3DXFilterTexture_ #endif // _IRR_COMPILE_WITH_DIRECT3D_8_ #ifdef _IRR_USE_D3DXFilterTexture_ #pragma comment(lib, "d3dx9.lib") #endif namespace irr { namespace video { //! rendertarget constructor CD3D9Texture::CD3D9Texture(CD3D9Driver* driver, const core::dimension2d& size, const io::path& name, const ECOLOR_FORMAT format) : ITexture(name), Texture(0), RTTSurface(0), Driver(driver), DepthSurface(0), TextureSize(size), ImageSize(size), Pitch(0), ColorFormat(ECF_UNKNOWN), HasMipMaps(false), HardwareMipMaps(false), IsRenderTarget(true) { #ifdef _DEBUG setDebugName("CD3D9Texture"); #endif Device=driver->getExposedVideoData().D3D9.D3DDev9; if (Device) Device->AddRef(); createRenderTarget(format); } //! constructor CD3D9Texture::CD3D9Texture(IImage* image, CD3D9Driver* driver, u32 flags, const io::path& name, void* mipmapData) : ITexture(name), Texture(0), RTTSurface(0), Driver(driver), DepthSurface(0), TextureSize(0,0), ImageSize(0,0), Pitch(0), ColorFormat(ECF_UNKNOWN), HasMipMaps(false), HardwareMipMaps(false), IsRenderTarget(false) { #ifdef _DEBUG setDebugName("CD3D9Texture"); #endif HasMipMaps = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); Device=driver->getExposedVideoData().D3D9.D3DDev9; if (Device) Device->AddRef(); if (image) { if (createTexture(flags, image)) { if (copyTexture(image)) { regenerateMipMapLevels(mipmapData); } } else os::Printer::log("Could not create DIRECT3D9 Texture.", ELL_WARNING); } } //! destructor CD3D9Texture::~CD3D9Texture() { if (Texture) Texture->Release(); if (RTTSurface) RTTSurface->Release(); // if this texture was the last one using the depth buffer // we can release the surface. We only use the value of the pointer // hence it is safe to use the dropped pointer... if (DepthSurface) { if (DepthSurface->drop()) Driver->removeDepthSurface(DepthSurface); } if (Device) Device->Release(); } void CD3D9Texture::createRenderTarget(const ECOLOR_FORMAT format) { // are texture size restrictions there ? if(!Driver->queryFeature(EVDF_TEXTURE_NPOT)) { if (TextureSize != ImageSize) os::Printer::log("RenderTarget size has to be a power of two", ELL_INFORMATION); } TextureSize = TextureSize.getOptimalSize(!Driver->queryFeature(EVDF_TEXTURE_NPOT), !Driver->queryFeature(EVDF_TEXTURE_NSQUARE), true, Driver->Caps.MaxTextureWidth); D3DFORMAT d3dformat = Driver->getD3DColorFormat(); if(ColorFormat == ECF_UNKNOWN) { // get irrlicht format from backbuffer // (This will get overwritten by the custom format if it is provided, else kept.) ColorFormat = Driver->getColorFormat(); setPitch(d3dformat); // Use color format if provided. if(format != ECF_UNKNOWN) { ColorFormat = format; d3dformat = Driver->getD3DFormatFromColorFormat(format); setPitch(d3dformat); // This will likely set pitch to 0 for now. } } else { d3dformat = Driver->getD3DFormatFromColorFormat(ColorFormat); } // create texture HRESULT hr; hr = Device->CreateTexture( TextureSize.Width, TextureSize.Height, 1, // mip map level count, we don't want mipmaps here D3DUSAGE_RENDERTARGET, d3dformat, D3DPOOL_DEFAULT, &Texture, NULL); if (FAILED(hr)) { if (D3DERR_INVALIDCALL == hr) os::Printer::log("Could not create render target texture", "Invalid Call"); else if (D3DERR_OUTOFVIDEOMEMORY == hr) os::Printer::log("Could not create render target texture", "Out of Video Memory"); else if (E_OUTOFMEMORY == hr) os::Printer::log("Could not create render target texture", "Out of Memory"); else os::Printer::log("Could not create render target texture"); } } bool CD3D9Texture::createMipMaps(u32 level) { if (level==0) return true; if (HardwareMipMaps && Texture) { // generate mipmaps in hardware Texture->GenerateMipSubLevels(); return true; } // manual mipmap generation IDirect3DSurface9* upperSurface = 0; IDirect3DSurface9* lowerSurface = 0; // get upper level HRESULT hr = Texture->GetSurfaceLevel(level-1, &upperSurface); if (FAILED(hr) || !upperSurface) { os::Printer::log("Could not get upper surface level for mip map generation", ELL_WARNING); return false; } // get lower level hr = Texture->GetSurfaceLevel(level, &lowerSurface); if (FAILED(hr) || !lowerSurface) { os::Printer::log("Could not get lower surface level for mip map generation", ELL_WARNING); upperSurface->Release(); return false; } D3DSURFACE_DESC upperDesc, lowerDesc; upperSurface->GetDesc(&upperDesc); lowerSurface->GetDesc(&lowerDesc); D3DLOCKED_RECT upperlr; D3DLOCKED_RECT lowerlr; // lock upper surface if (FAILED(upperSurface->LockRect(&upperlr, NULL, 0))) { upperSurface->Release(); lowerSurface->Release(); os::Printer::log("Could not lock upper texture for mip map generation", ELL_WARNING); return false; } // lock lower surface if (FAILED(lowerSurface->LockRect(&lowerlr, NULL, 0))) { upperSurface->UnlockRect(); upperSurface->Release(); lowerSurface->Release(); os::Printer::log("Could not lock lower texture for mip map generation", ELL_WARNING); return false; } if (upperDesc.Format != lowerDesc.Format) { os::Printer::log("Cannot copy mip maps with different formats.", ELL_WARNING); } else { if ((upperDesc.Format == D3DFMT_A1R5G5B5) || (upperDesc.Format == D3DFMT_R5G6B5)) copy16BitMipMap((char*)upperlr.pBits, (char*)lowerlr.pBits, lowerDesc.Width, lowerDesc.Height, upperlr.Pitch, lowerlr.Pitch); else if (upperDesc.Format == D3DFMT_A8R8G8B8) copy32BitMipMap((char*)upperlr.pBits, (char*)lowerlr.pBits, lowerDesc.Width, lowerDesc.Height, upperlr.Pitch, lowerlr.Pitch); else os::Printer::log("Unsupported mipmap format, cannot copy.", ELL_WARNING); } bool result=true; // unlock if (FAILED(upperSurface->UnlockRect())) result=false; if (FAILED(lowerSurface->UnlockRect())) result=false; // release upperSurface->Release(); lowerSurface->Release(); if (!result || (upperDesc.Width <= 3 && upperDesc.Height <= 3)) return result; // stop generating levels // generate next level return createMipMaps(level+1); } //! creates the hardware texture bool CD3D9Texture::createTexture(u32 flags, IImage * image) { ImageSize = image->getDimension(); core::dimension2d optSize = ImageSize.getOptimalSize(!Driver->queryFeature(EVDF_TEXTURE_NPOT), !Driver->queryFeature(EVDF_TEXTURE_NSQUARE), true, Driver->Caps.MaxTextureWidth); D3DFORMAT format = D3DFMT_A1R5G5B5; switch(getTextureFormatFromFlags(flags)) { case ETCF_ALWAYS_16_BIT: format = D3DFMT_A1R5G5B5; break; case ETCF_ALWAYS_32_BIT: format = D3DFMT_A8R8G8B8; break; case ETCF_OPTIMIZED_FOR_QUALITY: { switch(image->getColorFormat()) { case ECF_R8G8B8: case ECF_A8R8G8B8: format = D3DFMT_A8R8G8B8; break; case ECF_A1R5G5B5: case ECF_R5G6B5: format = D3DFMT_A1R5G5B5; break; } } break; case ETCF_OPTIMIZED_FOR_SPEED: format = D3DFMT_A1R5G5B5; break; default: break; } if (Driver->getTextureCreationFlag(video::ETCF_NO_ALPHA_CHANNEL)) { if (format == D3DFMT_A8R8G8B8) format = D3DFMT_R8G8B8; else if (format == D3DFMT_A1R5G5B5) format = D3DFMT_R5G6B5; } const bool mipmaps = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); DWORD usage = 0; // This enables hardware mip map generation. if (mipmaps && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE)) { LPDIRECT3D9 intf = Driver->getExposedVideoData().D3D9.D3D9; D3DDISPLAYMODE d3ddm; intf->GetAdapterDisplayMode(Driver->DisplayAdapter, &d3ddm); if (D3D_OK==intf->CheckDeviceFormat(Driver->DisplayAdapter,D3DDEVTYPE_HAL,d3ddm.Format,D3DUSAGE_AUTOGENMIPMAP,D3DRTYPE_TEXTURE,format)) { usage = D3DUSAGE_AUTOGENMIPMAP; HardwareMipMaps = true; } } HRESULT hr = Device->CreateTexture(optSize.Width, optSize.Height, mipmaps ? 0 : 1, // number of mipmaplevels (0 = automatic all) usage, // usage format, D3DPOOL_MANAGED , &Texture, NULL); if (FAILED(hr)) { // try brute force 16 bit HardwareMipMaps = false; if (format == D3DFMT_A8R8G8B8) format = D3DFMT_A1R5G5B5; else if (format == D3DFMT_R8G8B8) format = D3DFMT_R5G6B5; else return false; hr = Device->CreateTexture(optSize.Width, optSize.Height, mipmaps ? 0 : 1, // number of mipmaplevels (0 = automatic all) 0, format, D3DPOOL_MANAGED, &Texture, NULL); } ColorFormat = Driver->getColorFormatFromD3DFormat(format); setPitch(format); return (SUCCEEDED(hr)); } //! copies the image to the texture bool CD3D9Texture::copyTexture(IImage * image) { if (Texture && image) { D3DSURFACE_DESC desc; Texture->GetLevelDesc(0, &desc); TextureSize.Width = desc.Width; TextureSize.Height = desc.Height; D3DLOCKED_RECT rect; HRESULT hr = Texture->LockRect(0, &rect, 0, 0); if (FAILED(hr)) { os::Printer::log("Texture data not copied", "Could not LockRect D3D9 Texture.", ELL_ERROR); return false; } Pitch = rect.Pitch; image->copyToScaling(rect.pBits, TextureSize.Width, TextureSize.Height, ColorFormat, Pitch); hr = Texture->UnlockRect(0); if (FAILED(hr)) { os::Printer::log("Texture data not copied", "Could not UnlockRect D3D9 Texture.", ELL_ERROR); return false; } } return true; } //! lock function void* CD3D9Texture::lock(E_TEXTURE_LOCK_MODE mode, u32 mipmapLevel) { if (!Texture) return 0; MipLevelLocked=mipmapLevel; HRESULT hr; D3DLOCKED_RECT rect; if(!IsRenderTarget) { hr = Texture->LockRect(mipmapLevel, &rect, 0, (mode==ETLM_READ_ONLY)?D3DLOCK_READONLY:0); if (FAILED(hr)) { os::Printer::log("Could not lock DIRECT3D9 Texture.", ELL_ERROR); return 0; } } else { if (!RTTSurface) { // Make RTT surface large enough for all miplevels (including 0) D3DSURFACE_DESC desc; Texture->GetLevelDesc(0, &desc); hr = Device->CreateOffscreenPlainSurface(desc.Width, desc.Height, desc.Format, D3DPOOL_SYSTEMMEM, &RTTSurface, 0); if (FAILED(hr)) { os::Printer::log("Could not lock DIRECT3D9 Texture", "Offscreen surface creation failed.", ELL_ERROR); return 0; } } IDirect3DSurface9 *surface = 0; hr = Texture->GetSurfaceLevel(mipmapLevel, &surface); if (FAILED(hr)) { os::Printer::log("Could not lock DIRECT3D9 Texture", "Could not get surface.", ELL_ERROR); return 0; } hr = Device->GetRenderTargetData(surface, RTTSurface); surface->Release(); if(FAILED(hr)) { os::Printer::log("Could not lock DIRECT3D9 Texture", "Data copy failed.", ELL_ERROR); return 0; } hr = RTTSurface->LockRect(&rect, 0, (mode==ETLM_READ_ONLY)?D3DLOCK_READONLY:0); if(FAILED(hr)) { os::Printer::log("Could not lock DIRECT3D9 Texture", "LockRect failed.", ELL_ERROR); return 0; } } return rect.pBits; } //! unlock function void CD3D9Texture::unlock() { if (!Texture) return; if (!IsRenderTarget) Texture->UnlockRect(MipLevelLocked); else if (RTTSurface) RTTSurface->UnlockRect(); } //! Returns original size of the texture. const core::dimension2d& CD3D9Texture::getOriginalSize() const { return ImageSize; } //! Returns (=size) of the texture. const core::dimension2d& CD3D9Texture::getSize() const { return TextureSize; } //! returns driver type of texture (=the driver, who created the texture) E_DRIVER_TYPE CD3D9Texture::getDriverType() const { return EDT_DIRECT3D9; } //! returns color format of texture ECOLOR_FORMAT CD3D9Texture::getColorFormat() const { return ColorFormat; } //! returns pitch of texture (in bytes) u32 CD3D9Texture::getPitch() const { return Pitch; } //! returns the DIRECT3D9 Texture IDirect3DBaseTexture9* CD3D9Texture::getDX9Texture() const { return Texture; } //! returns if texture has mipmap levels bool CD3D9Texture::hasMipMaps() const { return HasMipMaps; } void CD3D9Texture::copy16BitMipMap(char* src, char* tgt, s32 width, s32 height, s32 pitchsrc, s32 pitchtgt) const { for (s32 y=0; y1) size.Width /=2; if (size.Height>1) size.Height /=2; ++level; IDirect3DSurface9* mipSurface = 0; HRESULT hr = Texture->GetSurfaceLevel(level, &mipSurface); if (FAILED(hr) || !mipSurface) { os::Printer::log("Could not get mipmap level", ELL_WARNING); return; } D3DSURFACE_DESC mipDesc; mipSurface->GetDesc(&mipDesc); D3DLOCKED_RECT miplr; // lock mipmap surface if (FAILED(mipSurface->LockRect(&miplr, NULL, 0))) { mipSurface->Release(); os::Printer::log("Could not lock texture", ELL_WARNING); return; } memcpy(miplr.pBits, mipmapData, size.getArea()*getPitch()/TextureSize.Width); mipmapData = (u8*)mipmapData+size.getArea()*getPitch()/TextureSize.Width; // unlock mipSurface->UnlockRect(); // release mipSurface->Release(); } while (size.Width != 1 || size.Height != 1); } else if (HasMipMaps) { // create mip maps. #ifdef _IRR_USE_D3DXFilterTexture_ // The D3DXFilterTexture function seems to get linked wrong when // compiling with both D3D8 and 9, causing it not to work in the D3D9 device. // So mipmapgeneration is replaced with my own bad generation HRESULT hr = D3DXFilterTexture(Texture, NULL, D3DX_DEFAULT, D3DX_DEFAULT); if (FAILED(hr)) #endif createMipMaps(); } } //! returns if it is a render target bool CD3D9Texture::isRenderTarget() const { return IsRenderTarget; } //! Returns pointer to the render target surface IDirect3DSurface9* CD3D9Texture::getRenderTargetSurface() { if (!IsRenderTarget) return 0; IDirect3DSurface9 *pRTTSurface = 0; if (Texture) Texture->GetSurfaceLevel(0, &pRTTSurface); if (pRTTSurface) pRTTSurface->Release(); return pRTTSurface; } void CD3D9Texture::setPitch(D3DFORMAT d3dformat) { switch(d3dformat) { case D3DFMT_X1R5G5B5: case D3DFMT_A1R5G5B5: Pitch = TextureSize.Width * 2; break; case D3DFMT_A8B8G8R8: case D3DFMT_A8R8G8B8: case D3DFMT_X8R8G8B8: Pitch = TextureSize.Width * 4; break; case D3DFMT_R5G6B5: Pitch = TextureSize.Width * 2; break; case D3DFMT_R8G8B8: Pitch = TextureSize.Width * 3; break; default: Pitch = 0; }; } } // end namespace video } // end namespace irr #endif // _IRR_COMPILE_WITH_DIRECT3D_9_