So, I went for another method - create two offscreen surfaces, use GetDC() on both, and then use the GDI StretchBlt() function to blit. So, here's my function:
PTexture::SP PTextureMgr::CreateScaledTexture(const EID& id, u32 nWidth, u32 nHeight, u32 nFlags, const u32* pData, u32 nDataWidth, u32 nDataHeight){ Assert(nWidth != nDataWidth || nHeight != nDataHeight, "Data size is the same as the texture size. Use CreateTexture()"); // Create an offscreen surface that is the same size as the data buffer LPDIRECT3DSURFACE9 pSourceSurf; HRESULT hResult = m_pDevice->CreateOffscreenPlainSurface(nDataWidth, nDataHeight, D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &pSourceSurf, NULL); if(FAILED(hResult)) { std::wstringstream str; str << L"Failed to create surface. Error: "; str << DXGetErrorString(hResult); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Lock surface D3DLOCKED_RECT rect; hResult = pSourceSurf->LockRect(&rect, NULL, 0); if(FAILED(hResult)) { pSourceSurf->Release(); std::wstringstream str; str << L"Failed to lock surface. Error: "; str << DXGetErrorString(hResult); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Make sure there's no alpha #ifdef BUILD_DEBUG for(u32 y=0; y { for(u32 x=0; x { Assert((pData[y*nDataWidth + x]&0xff000000)==0xff000000, "Can't use CreateScaledTexture() when source data has an alpha channel"); } } #endif // Fill - do a fast copy if we can if((u32)rect.Pitch == nDataWidth * 4) { memcpy(rect.pBits, pData, nDataWidth * nDataHeight * 4); } else { for(UINT y=0; y { memcpy((BYTE*)rect.pBits + y*rect.Pitch, pData, nDataWidth * 4); pData += nDataWidth; } } // Unlock pSourceSurf->UnlockRect(); // Create target texture - Remove mipmap flag, we'll generate them once texture is filled PTexture::SP pTexture = CreateTexture(id, nWidth, nHeight, nFlags& (~ETexture::FLAG_GenerateMipMaps)); if(!pTexture) { pSourceSurf->Release(); return 0; } // We can't StretchRect() to the texture because it's not in the default // pool. We can't StretchRect() to another surface because you can't StretchRect() // between two offscreen surfaces. The only choice is to use GetDC() and GDI // StretchBlt() between two surfaces, then to update the texture. // Create offscreen surface the same size as the target texture LPDIRECT3DSURFACE9 pDestSurf; hResult = m_pDevice->CreateOffscreenPlainSurface(nWidth, nHeight, D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &pDestSurf, NULL); if(FAILED(hResult)) { pSourceSurf->Release(); std::wstringstream str; str << L"Failed to create surface. Error: "; str << DXGetErrorString(hResult); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Get the DC for both surfaces HDC hDCSource, hDCDest; hResult = pSourceSurf->GetDC(&hDCSource); if(FAILED(hResult)) { pDestSurf->Release(); pSourceSurf->Release(); std::wstringstream str; str << L"Failed to create surface. Error: "; str << DXGetErrorString(hResult); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } hResult = pDestSurf->GetDC(&hDCDest); if(FAILED(hResult)) { pSourceSurf->ReleaseDC(hDCSource); pDestSurf->Release(); pSourceSurf->Release(); std::wstringstream str; str << L"Failed to create surface. Error: "; str << DXGetErrorString(hResult); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Check we support StretchBlt int nCaps = GetDeviceCaps(hDCDest, RASTERCAPS); if(!(nCaps & RC_STRETCHBLT)) { pDestSurf->ReleaseDC(hDCDest); pSourceSurf->ReleaseDC(hDCSource); pDestSurf->Release(); pSourceSurf->Release(); std::wstringstream str; str << L"StretchBlt() is not supported on this hardware"; str << DXGetErrorString(GetLastError()); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Blit if(!StretchBlt(hDCDest, 0, 0, nWidth, nHeight, hDCSource, 0, 0, nDataWidth, nDataHeight, SRCCOPY)) { pDestSurf->ReleaseDC(hDCDest); pSourceSurf->ReleaseDC(hDCSource); pDestSurf->Release(); pSourceSurf->Release(); std::wstringstream str; str << L"StretchBlt() failed. Error: "; str << DXGetErrorString(GetLastError()); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Done with DCs and source surface now pDestSurf->ReleaseDC(hDCDest); pSourceSurf->ReleaseDC(hDCSource); pSourceSurf->Release(); // Get top level surface of texture LPDIRECT3DSURFACE9 pTextureSurface; hResult = pTexture->GetTexture()->GetSurfaceLevel(0, &pTextureSurface); if(FAILED(hResult)) { pDestSurf->Release(); std::wstringstream str; str << L"GetSurfaceLevel failed. Error: "; str << DXGetErrorString(hResult); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Lock dest surface (Which is now the source surface for the copy) D3DLOCKED_RECT rectSrc; hResult = pDestSurf->LockRect(&rectSrc, NULL, D3DLOCK_READONLY); if(FAILED(hResult)) { pDestSurf->Release(); pTextureSurface->Release(); std::wstringstream str; str << L"Failed to lock surface. Error: "; str << DXGetErrorString(hResult); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Lock texture surface (Which is the dest surface for the copy) D3DLOCKED_RECT rectDest; hResult = pTextureSurface->LockRect(&rectDest, NULL, 0); if(FAILED(hResult)) { pDestSurf->UnlockRect(); pTextureSurface->Release(); pDestSurf->Release(); std::wstringstream str; str << L"Failed to lock surface. Error: "; str << DXGetErrorString(hResult); PApp::Get().SetError(str.str()); ELog::Get().ErrorFormat(L"TEXMGR : %s\n", PApp::Get().GetError().c_str()); return 0; } // Get format for texture PPixelFormat fmt; if(!GetPixelFormat(m_fmtTextures, fmt)) { Assert(false, "GetPixelFormat failed!"); } Assert(fmt.nBpp == 32, "Need to account for non-32bit textures"); // Copy scanlines - do a fast copy if we can if((u32)rectSrc.Pitch == nWidth * fmt.nBpp/8 && (u32)rectDest.Pitch == nWidth * fmt.nBpp/8) { memcpy(rectDest.pBits, rectSrc.pBits, rectSrc.Pitch * nHeight); } else { u8* pDest = (u8*)rectDest.pBits; u8* pSource = (u8*)rectSrc.pBits; for(UINT y=0; y { memcpy(pDest, pSource, nWidth * fmt.nBpp/8); pDest += rectDest.Pitch; pSource += rectSrc.Pitch; } } // Unlock and release surfaces pTextureSurface->UnlockRect(); pDestSurf->UnlockRect(); pTextureSurface->Release(); pDestSurf->Release(); // Generate mipmaps if needed, and return texture if(nFlags & ETexture::FLAG_GenerateMipMaps) pTexture->GetTexture()->GenerateMipSubLevels(); return pTexture;}
I loose alpha from that, but it's not too bad. Still, there has to be a neater / faster way of doing it, anyone got any ideas?
I just wrote my own texture scaling code (rounding up to the next largest valid texture size).