//------------------------------------------------------------------------------------- // DirectXTexImage.cpp // // DirectX Texture Library - Image container // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/?LinkId=248926 //------------------------------------------------------------------------------------- #include "directxtexp.h" namespace DirectX { extern bool _CalculateMipLevels( _In_ size_t width, _In_ size_t height, _Inout_ size_t& mipLevels ); extern bool _CalculateMipLevels3D( _In_ size_t width, _In_ size_t height, _In_ size_t depth, _Inout_ size_t& mipLevels ); extern bool _IsAlphaAllOpaqueBC( _In_ const Image& cImage ); //------------------------------------------------------------------------------------- // Determines number of image array entries and pixel size //------------------------------------------------------------------------------------- _Use_decl_annotations_ void _DetermineImageArray( const TexMetadata& metadata, DWORD cpFlags, size_t& nImages, size_t& pixelSize ) { assert( metadata.width > 0 && metadata.height > 0 && metadata.depth > 0 ); assert( metadata.arraySize > 0 ); assert( metadata.mipLevels > 0 ); size_t _pixelSize = 0; size_t _nimages = 0; switch( metadata.dimension ) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: for( size_t item = 0; item < metadata.arraySize; ++item ) { size_t w = metadata.width; size_t h = metadata.height; for( size_t level=0; level < metadata.mipLevels; ++level ) { size_t rowPitch, slicePitch; ComputePitch( metadata.format, w, h, rowPitch, slicePitch, cpFlags ); _pixelSize += slicePitch; ++_nimages; if ( h > 1 ) h >>= 1; if ( w > 1 ) w >>= 1; } } break; case TEX_DIMENSION_TEXTURE3D: { size_t w = metadata.width; size_t h = metadata.height; size_t d = metadata.depth; for( size_t level=0; level < metadata.mipLevels; ++level ) { size_t rowPitch, slicePitch; ComputePitch( metadata.format, w, h, rowPitch, slicePitch, cpFlags ); for( size_t slice=0; slice < d; ++slice ) { _pixelSize += slicePitch; ++_nimages; } if ( h > 1 ) h >>= 1; if ( w > 1 ) w >>= 1; if ( d > 1 ) d >>= 1; } } break; default: assert( false ); break; } nImages = _nimages; pixelSize = _pixelSize; } //------------------------------------------------------------------------------------- // Fills in the image array entries //------------------------------------------------------------------------------------- _Use_decl_annotations_ bool _SetupImageArray( uint8_t *pMemory, size_t pixelSize, const TexMetadata& metadata, DWORD cpFlags, Image* images, size_t nImages ) { assert( pMemory ); assert( pixelSize > 0 ); assert( nImages > 0 ); if ( !images ) return false; size_t index = 0; uint8_t* pixels = pMemory; const uint8_t* pEndBits = pMemory + pixelSize; switch( metadata.dimension ) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: if (metadata.arraySize == 0 || metadata.mipLevels == 0) { return false; } for( size_t item = 0; item < metadata.arraySize; ++item ) { size_t w = metadata.width; size_t h = metadata.height; for( size_t level=0; level < metadata.mipLevels; ++level ) { if ( index >= nImages ) { return false; } size_t rowPitch, slicePitch; ComputePitch( metadata.format, w, h, rowPitch, slicePitch, cpFlags ); images[index].width = w; images[index].height = h; images[index].format = metadata.format; images[index].rowPitch = rowPitch; images[index].slicePitch = slicePitch; images[index].pixels = pixels; ++index; pixels += slicePitch; if ( pixels > pEndBits ) { return false; } if ( h > 1 ) h >>= 1; if ( w > 1 ) w >>= 1; } } return true; case TEX_DIMENSION_TEXTURE3D: { if (metadata.mipLevels == 0 || metadata.depth == 0) { return false; } size_t w = metadata.width; size_t h = metadata.height; size_t d = metadata.depth; for( size_t level=0; level < metadata.mipLevels; ++level ) { size_t rowPitch, slicePitch; ComputePitch( metadata.format, w, h, rowPitch, slicePitch, cpFlags ); for( size_t slice=0; slice < d; ++slice ) { if ( index >= nImages ) { return false; } // We use the same memory organization that Direct3D 11 needs for D3D11_SUBRESOURCE_DATA // with all slices of a given miplevel being continuous in memory images[index].width = w; images[index].height = h; images[index].format = metadata.format; images[index].rowPitch = rowPitch; images[index].slicePitch = slicePitch; images[index].pixels = pixels; ++index; pixels += slicePitch; if ( pixels > pEndBits ) { return false; } } if ( h > 1 ) h >>= 1; if ( w > 1 ) w >>= 1; if ( d > 1 ) d >>= 1; } } return true; default: return false; } } //===================================================================================== // ScratchImage - Bitmap image container //===================================================================================== //------------------------------------------------------------------------------------- // Methods //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT ScratchImage::Initialize( const TexMetadata& mdata ) { if ( !IsValid(mdata.format) || IsVideo(mdata.format) ) return E_INVALIDARG; size_t mipLevels = mdata.mipLevels; switch( mdata.dimension ) { case TEX_DIMENSION_TEXTURE1D: if ( !mdata.width || mdata.height != 1 || mdata.depth != 1 || !mdata.arraySize ) return E_INVALIDARG; if ( !_CalculateMipLevels(mdata.width,1,mipLevels) ) return E_INVALIDARG; break; case TEX_DIMENSION_TEXTURE2D: if ( !mdata.width || !mdata.height || mdata.depth != 1 || !mdata.arraySize ) return E_INVALIDARG; if ( mdata.IsCubemap() ) { if ( (mdata.arraySize % 6) != 0 ) return E_INVALIDARG; } if ( !_CalculateMipLevels(mdata.width,mdata.height,mipLevels) ) return E_INVALIDARG; break; case TEX_DIMENSION_TEXTURE3D: if ( !mdata.width || !mdata.height || !mdata.depth || mdata.arraySize != 1 ) return E_INVALIDARG; if ( !_CalculateMipLevels3D(mdata.width,mdata.height,mdata.depth,mipLevels) ) return E_INVALIDARG; break; default: return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } Release(); _metadata.width = mdata.width; _metadata.height = mdata.height; _metadata.depth = mdata.depth; _metadata.arraySize = mdata.arraySize; _metadata.mipLevels = mipLevels; _metadata.miscFlags = mdata.miscFlags; _metadata.miscFlags2 = mdata.miscFlags2; _metadata.format = mdata.format; _metadata.dimension = mdata.dimension; size_t pixelSize, nimages; _DetermineImageArray( _metadata, CP_FLAGS_NONE, nimages, pixelSize ); _image = new (std::nothrow) Image[ nimages ]; if ( !_image ) return E_OUTOFMEMORY; _nimages = nimages; memset( _image, 0, sizeof(Image) * nimages ); _memory = reinterpret_cast( _aligned_malloc( pixelSize, 16 ) ); if ( !_memory ) { Release(); return E_OUTOFMEMORY; } _size = pixelSize; if ( !_SetupImageArray( _memory, pixelSize, _metadata, CP_FLAGS_NONE, _image, nimages ) ) { Release(); return E_FAIL; } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::Initialize1D( DXGI_FORMAT fmt, size_t length, size_t arraySize, size_t mipLevels ) { if ( !IsValid(fmt) || IsVideo(fmt) || !length || !arraySize ) return E_INVALIDARG; // 1D is a special case of the 2D case HRESULT hr = Initialize2D( fmt, length, 1, arraySize, mipLevels ); if ( FAILED(hr) ) return hr; _metadata.dimension = TEX_DIMENSION_TEXTURE1D; return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::Initialize2D( DXGI_FORMAT fmt, size_t width, size_t height, size_t arraySize, size_t mipLevels ) { if ( !IsValid(fmt) || IsVideo(fmt) || !width || !height || !arraySize ) return E_INVALIDARG; if ( !_CalculateMipLevels(width,height,mipLevels) ) return E_INVALIDARG; Release(); _metadata.width = width; _metadata.height = height; _metadata.depth = 1; _metadata.arraySize = arraySize; _metadata.mipLevels = mipLevels; _metadata.miscFlags = 0; _metadata.miscFlags2 = 0; _metadata.format = fmt; _metadata.dimension = TEX_DIMENSION_TEXTURE2D; size_t pixelSize, nimages; _DetermineImageArray( _metadata, CP_FLAGS_NONE, nimages, pixelSize ); _image = new (std::nothrow) Image[ nimages ]; if ( !_image ) return E_OUTOFMEMORY; _nimages = nimages; memset( _image, 0, sizeof(Image) * nimages ); _memory = reinterpret_cast( _aligned_malloc( pixelSize, 16 ) ); if ( !_memory ) { Release(); return E_OUTOFMEMORY; } _size = pixelSize; if ( !_SetupImageArray( _memory, pixelSize, _metadata, CP_FLAGS_NONE, _image, nimages ) ) { Release(); return E_FAIL; } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::Initialize3D( DXGI_FORMAT fmt, size_t width, size_t height, size_t depth, size_t mipLevels ) { if ( !IsValid(fmt) || IsVideo(fmt) || !width || !height || !depth ) return E_INVALIDARG; if ( !_CalculateMipLevels3D(width,height,depth,mipLevels) ) return E_INVALIDARG; Release(); _metadata.width = width; _metadata.height = height; _metadata.depth = depth; _metadata.arraySize = 1; // Direct3D 10.x/11 does not support arrays of 3D textures _metadata.mipLevels = mipLevels; _metadata.miscFlags = 0; _metadata.miscFlags2 = 0; _metadata.format = fmt; _metadata.dimension = TEX_DIMENSION_TEXTURE3D; size_t pixelSize, nimages; _DetermineImageArray( _metadata, CP_FLAGS_NONE, nimages, pixelSize ); _image = new (std::nothrow) Image[ nimages ]; if ( !_image ) { Release(); return E_OUTOFMEMORY; } _nimages = nimages; memset( _image, 0, sizeof(Image) * nimages ); _memory = reinterpret_cast( _aligned_malloc( pixelSize, 16 ) ); if ( !_memory ) { Release(); return E_OUTOFMEMORY; } _size = pixelSize; if ( !_SetupImageArray( _memory, pixelSize, _metadata, CP_FLAGS_NONE, _image, nimages ) ) { Release(); return E_FAIL; } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::InitializeCube( DXGI_FORMAT fmt, size_t width, size_t height, size_t nCubes, size_t mipLevels ) { if ( !IsValid(fmt) || IsVideo(fmt) || !width || !height || !nCubes ) return E_INVALIDARG; // A DirectX11 cubemap is just a 2D texture array that is a multiple of 6 for each cube HRESULT hr = Initialize2D( fmt, width, height, nCubes * 6, mipLevels ); if ( FAILED(hr) ) return hr; _metadata.miscFlags |= TEX_MISC_TEXTURECUBE; return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::InitializeFromImage( const Image& srcImage, bool allow1D ) { HRESULT hr = ( srcImage.height > 1 || !allow1D ) ? Initialize2D( srcImage.format, srcImage.width, srcImage.height, 1, 1 ) : Initialize1D( srcImage.format, srcImage.width, 1, 1 ); if ( FAILED(hr) ) return hr; const uint8_t* sptr = reinterpret_cast( srcImage.pixels ); if ( !sptr ) return E_POINTER; auto dptr = reinterpret_cast( _image[0].pixels ); if ( !dptr ) return E_POINTER; for( size_t y = 0; y < srcImage.height; ++y ) { _CopyScanline( dptr, _image[0].rowPitch, sptr, srcImage.rowPitch, srcImage.format, TEXP_SCANLINE_NONE ); sptr += srcImage.rowPitch; dptr += _image[0].rowPitch; } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::InitializeArrayFromImages( const Image* images, size_t nImages, bool allow1D ) { if ( !images || !nImages ) return E_INVALIDARG; DXGI_FORMAT format = images[0].format; size_t width = images[0].width; size_t height = images[0].height; for( size_t index=0; index < nImages; ++index ) { if ( !images[index].pixels ) return E_POINTER; if ( images[index].format != format || images[index].width != width || images[index].height != height ) { // All images must be the same format, width, and height return E_FAIL; } } HRESULT hr = ( height > 1 || !allow1D ) ? Initialize2D( format, width, height, nImages, 1 ) : Initialize1D( format, width, nImages, 1 ); if ( FAILED(hr) ) return hr; for( size_t index=0; index < nImages; ++index ) { auto sptr = reinterpret_cast( images[index].pixels ); if ( !sptr ) return E_POINTER; assert( index < _nimages ); auto dptr = reinterpret_cast( _image[index].pixels ); if ( !dptr ) return E_POINTER; for( size_t y = 0; y < height; ++y ) { _CopyScanline( dptr, _image[index].rowPitch, sptr, images[index].rowPitch, format, TEXP_SCANLINE_NONE ); sptr += images[index].rowPitch; dptr += _image[index].rowPitch; } } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::InitializeCubeFromImages( const Image* images, size_t nImages ) { if ( !images || !nImages ) return E_INVALIDARG; // A DirectX11 cubemap is just a 2D texture array that is a multiple of 6 for each cube if ( ( nImages % 6 ) != 0 ) return E_INVALIDARG; HRESULT hr = InitializeArrayFromImages( images, nImages, false ); if ( FAILED(hr) ) return hr; _metadata.miscFlags |= TEX_MISC_TEXTURECUBE; return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::Initialize3DFromImages( const Image* images, size_t depth ) { if ( !images || !depth ) return E_INVALIDARG; DXGI_FORMAT format = images[0].format; size_t width = images[0].width; size_t height = images[0].height; for( size_t slice=0; slice < depth; ++slice ) { if ( !images[slice].pixels ) return E_POINTER; if ( images[slice].format != format || images[slice].width != width || images[slice].height != height ) { // All images must be the same format, width, and height return E_FAIL; } } HRESULT hr = Initialize3D( format, width, height, depth, 1 ); if ( FAILED(hr) ) return hr; for( size_t slice=0; slice < depth; ++slice ) { auto sptr = reinterpret_cast( images[slice].pixels ); if ( !sptr ) return E_POINTER; assert( slice < _nimages ); auto dptr = reinterpret_cast( _image[slice].pixels ); if ( !dptr ) return E_POINTER; for( size_t y = 0; y < height; ++y ) { _CopyScanline( dptr, _image[slice].rowPitch, sptr, images[slice].rowPitch, format, TEXP_SCANLINE_NONE ); sptr += images[slice].rowPitch; dptr += _image[slice].rowPitch; } } return S_OK; } void ScratchImage::Release() { _nimages = 0; _size = 0; if ( _image ) { delete [] _image; _image = 0; } if ( _memory ) { _aligned_free( _memory ); _memory = 0; } memset(&_metadata, 0, sizeof(_metadata)); } _Use_decl_annotations_ bool ScratchImage::OverrideFormat( DXGI_FORMAT f ) { if ( !_image ) return false; if ( !IsValid( f ) || IsVideo( f ) ) return false; if ( ( BitsPerPixel( f ) != BitsPerPixel( _metadata.format ) ) || ( IsCompressed( f ) != IsCompressed( _metadata.format ) ) || ( IsPacked( f ) != IsPacked( _metadata.format ) ) ) { // Can't change the effective pitch of the format this way return false; } for( size_t index = 0; index < _nimages; ++index ) { _image[ index ].format = f; } _metadata.format = f; return true; } _Use_decl_annotations_ const Image* ScratchImage::GetImage(size_t mip, size_t item, size_t slice) const { if ( mip >= _metadata.mipLevels ) return nullptr; size_t index = 0; switch( _metadata.dimension ) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: if ( slice > 0 ) return nullptr; if ( item >= _metadata.arraySize ) return nullptr; index = item*( _metadata.mipLevels ) + mip; break; case TEX_DIMENSION_TEXTURE3D: if ( item > 0 ) { // No support for arrays of volumes return nullptr; } else { size_t d = _metadata.depth; for( size_t level = 0; level < mip; ++level ) { index += d; if ( d > 1 ) d >>= 1; } if ( slice >= d ) return nullptr; index += slice; } break; default: return nullptr; } return &_image[index]; } bool ScratchImage::IsAlphaAllOpaque() const { if ( !HasAlpha( _metadata.format ) ) return true; if ( IsCompressed( _metadata.format ) ) { for( size_t index = 0; index < _nimages; ++index ) { if ( !_IsAlphaAllOpaqueBC( _image[ index ] ) ) return false; } } else { ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( (sizeof(XMVECTOR)*_metadata.width), 16 ) ) ); if ( !scanline ) return false; static const XMVECTORF32 threshold = { 0.99f, 0.99f, 0.99f, 0.99f }; for( size_t index = 0; index < _nimages; ++index ) { const Image& img = _image[ index ]; const uint8_t *pPixels = img.pixels; assert( pPixels ); for( size_t h = 0; h < img.height; ++h ) { if ( !_LoadScanline( scanline.get(), img.width, pPixels, img.rowPitch, img.format ) ) return false; XMVECTOR* ptr = scanline.get(); for( size_t w = 0; w < img.width; ++w ) { XMVECTOR alpha = XMVectorSplatW( *ptr ); if ( XMVector4Less( alpha, threshold ) ) return false; ++ptr; } pPixels += img.rowPitch; } } } return true; } }; // namespace