diff --git a/DirectXTex/DirectXTex.h b/DirectXTex/DirectXTex.h index 8e0169d..4c50b48 100644 --- a/DirectXTex/DirectXTex.h +++ b/DirectXTex/DirectXTex.h @@ -27,7 +27,7 @@ #include #include -#define DIRECTX_TEX_VERSION 102 +#define DIRECTX_TEX_VERSION 110 #if defined(_MSC_VER) && (_MSC_VER<1610) && !defined(_In_reads_) #define _Analysis_assume_(exp) @@ -403,6 +403,7 @@ namespace DirectX TEX_FILTER_CUBIC = 0x300000, TEX_FILTER_BOX = 0x400000, TEX_FILTER_FANT = 0x400000, // Equiv to Box filtering for mipmap generation + TEX_FILTER_TRIANGLE = 0x500000, // Filtering mode to use for any required image resizing TEX_FILTER_SRGB_IN = 0x1000000, diff --git a/DirectXTex/DirectXTexMipmaps.cpp b/DirectXTex/DirectXTexMipmaps.cpp index c83569a..379a662 100644 --- a/DirectXTex/DirectXTexMipmaps.cpp +++ b/DirectXTex/DirectXTexMipmaps.cpp @@ -417,6 +417,10 @@ static bool _UseWICFiltering( _In_ DXGI_FORMAT format, _In_ DWORD filter ) return false; } break; + + case TEX_FILTER_TRIANGLE: + // WIC does not implement this filter + return false; } return true; @@ -1077,6 +1081,193 @@ static HRESULT _Generate2DMipsCubicFilter( _In_ size_t levels, _In_ DWORD filter } +//--- 2D Triangle Filter --- +static HRESULT _Generate2DMipsTriangleFilter( _In_ size_t levels, _In_ DWORD filter, _In_ const ScratchImage& mipChain, _In_ size_t item ) +{ + if ( !mipChain.GetImages() ) + return E_INVALIDARG; + + using namespace TriangleFilter; + + // This assumes that the base image is already placed into the mipChain at the top level... (see _Setup2DMips) + + assert( levels > 1 ); + + size_t width = mipChain.GetMetadata().width; + size_t height = mipChain.GetMetadata().height; + + // Allocate initial temporary space (1 scanline, accumulation rows, plus X and Y filters) + ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( sizeof(XMVECTOR) * width, 16 ) ) ); + if ( !scanline ) + return E_OUTOFMEMORY; + + std::unique_ptr rowActive( new (std::nothrow) TriangleRow[ height ] ); + if ( !rowActive ) + return E_OUTOFMEMORY; + + TriangleRow * rowFree = nullptr; + + std::unique_ptr tfX, tfY; + + XMVECTOR* row = scanline.get(); + + // Resize base image to each target mip level + for( size_t level=1; level < levels; ++level ) + { + // 2D triangle filter + const Image* src = mipChain.GetImage( level-1, item, 0 ); + const Image* dest = mipChain.GetImage( level, item, 0 ); + + if ( !src || !dest ) + return E_POINTER; + + const uint8_t* pSrc = src->pixels; + size_t rowPitch = src->rowPitch; + const uint8_t* pEndSrc = pSrc + rowPitch * height; + + uint8_t* pDest = dest->pixels; + + size_t nwidth = (width > 1) ? (width >> 1) : 1; + HRESULT hr = _Create( width, nwidth, (filter & TEX_FILTER_WRAP_U) != 0, tfX ); + if ( FAILED(hr) ) + return hr; + + size_t nheight = (height > 1) ? (height >> 1) : 1; + hr = _Create( height, nheight, (filter & TEX_FILTER_WRAP_V) != 0, tfY ); + if ( FAILED(hr) ) + return hr; + +#ifdef _DEBUG + memset( row, 0xCD, sizeof(XMVECTOR)*width ); +#endif + + auto xFromEnd = reinterpret_cast( reinterpret_cast( tfX.get() ) + tfX->sizeInBytes ); + auto yFromEnd = reinterpret_cast( reinterpret_cast( tfY.get() ) + tfY->sizeInBytes ); + + // Count times rows get written (and clear out any leftover accumulation rows from last miplevel) + for( FilterFrom* yFrom = tfY->from; yFrom < yFromEnd; ) + { + for ( size_t j = 0; j < yFrom->count; ++j ) + { + size_t v = yFrom->to[ j ].u; + assert( v < nheight ); + TriangleRow* rowAcc = &rowActive.get()[ v ]; + + ++rowAcc->remaining; + + if ( rowAcc->scanline ) + { + memset( rowAcc->scanline.get(), 0, sizeof(XMVECTOR) * nwidth ); + } + } + + yFrom = reinterpret_cast( reinterpret_cast( yFrom ) + yFrom->sizeInBytes ); + } + + // Filter image + for( FilterFrom* yFrom = tfY->from; yFrom < yFromEnd; ) + { + // Create accumulation rows as needed + for ( size_t j = 0; j < yFrom->count; ++j ) + { + size_t v = yFrom->to[ j ].u; + assert( v < nheight ); + TriangleRow* rowAcc = &rowActive.get()[ v ]; + + if ( !rowAcc->scanline ) + { + if ( rowFree ) + { + // Steal and reuse scanline from 'free row' list + // (it will always be at least as wide as nwidth due to loop decending order) + assert( rowFree->scanline != 0 ); + rowAcc->scanline.reset( rowFree->scanline.release() ); + rowFree = rowFree->next; + } + else + { + rowAcc->scanline.reset( reinterpret_cast( _aligned_malloc( sizeof(XMVECTOR) * nwidth, 16 ) ) ); + if ( !rowAcc->scanline ) + return E_OUTOFMEMORY; + } + + memset( rowAcc->scanline.get(), 0, sizeof(XMVECTOR) * nwidth ); + } + } + + // Load source scanline + if ( (pSrc + rowPitch) > pEndSrc ) + return E_FAIL; + + if ( !_LoadScanlineLinear( row, width, pSrc, rowPitch, src->format, filter ) ) + return E_FAIL; + + pSrc += rowPitch; + + // Process row + size_t x = 0; + for( FilterFrom* xFrom = tfX->from; xFrom < xFromEnd; ++x ) + { + for ( size_t j = 0; j < yFrom->count; ++j ) + { + size_t v = yFrom->to[ j ].u; + assert( v < nheight ); + float yweight = yFrom->to[ j ].weight; + + XMVECTOR* accPtr = rowActive[ v ].scanline.get(); + if ( !accPtr ) + return E_POINTER; + + for ( size_t k = 0; k < xFrom->count; ++k ) + { + size_t u = xFrom->to[ k ].u; + assert( u < nwidth ); + + XMVECTOR weight = XMVectorReplicate( yweight * xFrom->to[ k ].weight ); + + assert( x < width ); + accPtr[ u ] = XMVectorMultiplyAdd( row[ x ], weight, accPtr[ u ] ); + } + } + + xFrom = reinterpret_cast( reinterpret_cast( xFrom ) + xFrom->sizeInBytes ); + } + + // Write completed accumulation rows + for ( size_t j = 0; j < yFrom->count; ++j ) + { + size_t v = yFrom->to[ j ].u; + assert( v < nheight ); + TriangleRow* rowAcc = &rowActive.get()[ v ]; + + assert( rowAcc->remaining > 0 ); + --rowAcc->remaining; + + if ( !rowAcc->remaining ) + { + if ( !_StoreScanlineLinear( pDest + (dest->rowPitch * v), dest->rowPitch, dest->format, rowAcc->scanline.get(), dest->width, filter ) ) + return E_FAIL; + + // Put row on freelist to reuse it's allocated scanline + rowAcc->next = rowFree; + rowFree = rowAcc; + } + } + + yFrom = reinterpret_cast( reinterpret_cast( yFrom ) + yFrom->sizeInBytes ); + } + + if ( height > 1 ) + height >>= 1; + + if ( width > 1 ) + width >>= 1; + } + + return S_OK; +} + + //------------------------------------------------------------------------------------- // Generate volume mip-map helpers //------------------------------------------------------------------------------------- @@ -2021,6 +2212,226 @@ static HRESULT _Generate3DMipsCubicFilter( _In_ size_t depth, _In_ size_t levels } +//--- 3D Triangle Filter --- +static HRESULT _Generate3DMipsTriangleFilter( _In_ size_t depth, _In_ size_t levels, _In_ DWORD filter, _In_ const ScratchImage& mipChain ) +{ + if ( !depth || !mipChain.GetImages() ) + return E_INVALIDARG; + + using namespace TriangleFilter; + + // This assumes that the base images are already placed into the mipChain at the top level... (see _Setup3DMips) + + assert( levels > 1 ); + + size_t width = mipChain.GetMetadata().width; + size_t height = mipChain.GetMetadata().height; + + // Allocate initial temporary space (1 scanline, accumulation rows, plus X/Y/Z filters) + ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( sizeof(XMVECTOR) * width, 16 ) ) ); + if ( !scanline ) + return E_OUTOFMEMORY; + + std::unique_ptr sliceActive( new (std::nothrow) TriangleRow[ depth ] ); + if ( !sliceActive ) + return E_OUTOFMEMORY; + + TriangleRow * sliceFree = nullptr; + + std::unique_ptr tfX, tfY, tfZ; + + XMVECTOR* row = scanline.get(); + + // Resize base image to each target mip level + for( size_t level=1; level < levels; ++level ) + { + size_t nwidth = (width > 1) ? (width >> 1) : 1; + HRESULT hr = _Create( width, nwidth, (filter & TEX_FILTER_WRAP_U) != 0, tfX ); + if ( FAILED(hr) ) + return hr; + + size_t nheight = (height > 1) ? (height >> 1) : 1; + hr = _Create( height, nheight, (filter & TEX_FILTER_WRAP_V) != 0, tfY ); + if ( FAILED(hr) ) + return hr; + + size_t ndepth = (depth > 1 ) ? (depth >> 1) : 1; + hr = _Create( depth, ndepth, (filter & TEX_FILTER_WRAP_W) != 0, tfZ ); + if ( FAILED(hr) ) + return hr; + +#ifdef _DEBUG + memset( row, 0xCD, sizeof(XMVECTOR)*width ); +#endif + + auto xFromEnd = reinterpret_cast( reinterpret_cast( tfX.get() ) + tfX->sizeInBytes ); + auto yFromEnd = reinterpret_cast( reinterpret_cast( tfY.get() ) + tfY->sizeInBytes ); + auto zFromEnd = reinterpret_cast( reinterpret_cast( tfZ.get() ) + tfZ->sizeInBytes ); + + // Count times slices get written (and clear out any leftover accumulation slices from last miplevel) + for( FilterFrom* zFrom = tfZ->from; zFrom < zFromEnd; ) + { + for ( size_t j = 0; j < zFrom->count; ++j ) + { + size_t w = zFrom->to[ j ].u; + assert( w < ndepth ); + TriangleRow* sliceAcc = &sliceActive.get()[ w ]; + + ++sliceAcc->remaining; + + if ( sliceAcc->scanline ) + { + memset( sliceAcc->scanline.get(), 0, sizeof(XMVECTOR) * nwidth * nheight ); + } + } + + zFrom = reinterpret_cast( reinterpret_cast( zFrom ) + zFrom->sizeInBytes ); + } + + // Filter image + size_t z = 0; + for( FilterFrom* zFrom = tfZ->from; zFrom < zFromEnd; ++z ) + { + // Create accumulation slices as needed + for ( size_t j = 0; j < zFrom->count; ++j ) + { + size_t w = zFrom->to[ j ].u; + assert( w < ndepth ); + TriangleRow* sliceAcc = &sliceActive.get()[ w ]; + + if ( !sliceAcc->scanline ) + { + if ( sliceFree ) + { + // Steal and reuse scanline from 'free slice' list + // (it will always be at least as large as nwidth*nheight due to loop decending order) + assert( sliceFree->scanline != 0 ); + sliceAcc->scanline.reset( sliceFree->scanline.release() ); + sliceFree = sliceFree->next; + } + else + { + sliceAcc->scanline.reset( reinterpret_cast( _aligned_malloc( sizeof(XMVECTOR) * nwidth * nheight, 16 ) ) ); + if ( !sliceAcc->scanline ) + return E_OUTOFMEMORY; + } + + memset( sliceAcc->scanline.get(), 0, sizeof(XMVECTOR) * nwidth * nheight ); + } + } + + assert( z < depth ); + const Image* src = mipChain.GetImage( level-1, 0, z ); + if ( !src ) + return E_POINTER; + + const uint8_t* pSrc = src->pixels; + size_t rowPitch = src->rowPitch; + const uint8_t* pEndSrc = pSrc + rowPitch * height; + + for( FilterFrom* yFrom = tfY->from; yFrom < yFromEnd; ) + { + // Load source scanline + if ( (pSrc + rowPitch) > pEndSrc ) + return E_FAIL; + + if ( !_LoadScanlineLinear( row, width, pSrc, rowPitch, src->format, filter ) ) + return E_FAIL; + + pSrc += rowPitch; + + // Process row + size_t x = 0; + for( FilterFrom* xFrom = tfX->from; xFrom < xFromEnd; ++x ) + { + for ( size_t j = 0; j < zFrom->count; ++j ) + { + size_t w = zFrom->to[ j ].u; + assert( w < ndepth ); + float zweight = zFrom->to[ j ].weight; + + XMVECTOR* accSlice = sliceActive[ w ].scanline.get(); + if ( !accSlice ) + return E_POINTER; + + for ( size_t k = 0; k < yFrom->count; ++k ) + { + size_t v = yFrom->to[ k ].u; + assert( v < nheight ); + float yweight = yFrom->to[ k ].weight; + + XMVECTOR * accPtr = accSlice + v * nwidth; + + for ( size_t l = 0; l < xFrom->count; ++l ) + { + size_t u = xFrom->to[ l ].u; + assert( u < nwidth ); + + XMVECTOR weight = XMVectorReplicate( zweight * yweight * xFrom->to[ l ].weight ); + + assert( x < width ); + accPtr[ u ] = XMVectorMultiplyAdd( row[ x ], weight, accPtr[ u ] ); + } + } + } + + xFrom = reinterpret_cast( reinterpret_cast( xFrom ) + xFrom->sizeInBytes ); + } + + yFrom = reinterpret_cast( reinterpret_cast( yFrom ) + yFrom->sizeInBytes ); + } + + // Write completed accumulation slices + for ( size_t j = 0; j < zFrom->count; ++j ) + { + size_t w = zFrom->to[ j ].u; + assert( w < ndepth ); + TriangleRow* sliceAcc = &sliceActive.get()[ w ]; + + assert( sliceAcc->remaining > 0 ); + --sliceAcc->remaining; + + if ( !sliceAcc->remaining ) + { + const Image* dest = mipChain.GetImage( level, 0, w ); + XMVECTOR* pAccSrc = sliceAcc->scanline.get(); + if ( !dest || !pAccSrc ) + return E_POINTER; + + uint8_t* pDest = dest->pixels; + + for( size_t h = 0; h < nheight; ++h ) + { + if ( !_StoreScanlineLinear( pDest, dest->rowPitch, dest->format, pAccSrc, dest->width, filter ) ) + return E_FAIL; + + pDest += dest->rowPitch; + pAccSrc += nwidth; + } + + // Put slice on freelist to reuse it's allocated scanline + sliceAcc->next = sliceFree; + sliceFree = sliceAcc; + } + } + + zFrom = reinterpret_cast( reinterpret_cast( zFrom ) + zFrom->sizeInBytes ); + } + + if ( height > 1 ) + height >>= 1; + + if ( width > 1 ) + width >>= 1; + + if ( depth > 1 ) + depth >>= 1; + } + + return S_OK; +} + + //===================================================================================== // Entry-points //===================================================================================== @@ -2172,6 +2583,16 @@ HRESULT GenerateMipMaps( const Image& baseImage, DWORD filter, size_t levels, Sc mipChain.Release(); return hr; + case TEX_FILTER_TRIANGLE: + hr = _Setup2DMips( &baseImage, 1, mdata, mipChain ); + if ( FAILED(hr) ) + return hr; + + hr = _Generate2DMipsTriangleFilter( levels, filter, mipChain, 0 ); + if ( FAILED(hr) ) + mipChain.Release(); + return hr; + default: return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } @@ -2359,6 +2780,19 @@ HRESULT GenerateMipMaps( const Image* srcImages, size_t nimages, const TexMetada } return hr; + case TEX_FILTER_TRIANGLE: + hr = _Setup2DMips( &baseImages[0], metadata.arraySize, mdata2, mipChain ); + if ( FAILED(hr) ) + return hr; + + for( size_t item = 0; item < metadata.arraySize; ++item ) + { + hr = _Generate2DMipsTriangleFilter( levels, filter, mipChain, item ); + if ( FAILED(hr) ) + mipChain.Release(); + } + return hr; + default: return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } @@ -2456,6 +2890,16 @@ HRESULT GenerateMipMaps3D( const Image* baseImages, size_t depth, DWORD filter, mipChain.Release(); return hr; + case TEX_FILTER_TRIANGLE: + hr = _Setup3DMips( baseImages, depth, levels, mipChain ); + if ( FAILED(hr) ) + return hr; + + hr = _Generate3DMipsTriangleFilter( depth, levels, filter, mipChain ); + if ( FAILED(hr) ) + mipChain.Release(); + return hr; + default: return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } @@ -2554,6 +2998,16 @@ HRESULT GenerateMipMaps3D( const Image* srcImages, size_t nimages, const TexMeta mipChain.Release(); return hr; + case TEX_FILTER_TRIANGLE: + hr = _Setup3DMips( &baseImages[0], metadata.depth, levels, mipChain ); + if ( FAILED(hr) ) + return hr; + + hr = _Generate3DMipsTriangleFilter( metadata.depth, levels, filter, mipChain ); + if ( FAILED(hr) ) + mipChain.Release(); + return hr; + default: return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } diff --git a/DirectXTex/DirectXTexResize.cpp b/DirectXTex/DirectXTexResize.cpp index cbe6ccd..14e9f4b 100644 --- a/DirectXTex/DirectXTexResize.cpp +++ b/DirectXTex/DirectXTexResize.cpp @@ -206,6 +206,10 @@ static bool _UseWICFiltering( _In_ DXGI_FORMAT format, _In_ DWORD filter ) return false; } break; + + case TEX_FILTER_TRIANGLE: + // WIC does not implement this filter + return false; } return true; @@ -582,6 +586,159 @@ static HRESULT _ResizeCubicFilter( _In_ const Image& srcImage, _In_ DWORD filter } +//--- Triangle Filter --- +static HRESULT _ResizeTriangleFilter( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) +{ + assert( srcImage.pixels && destImage.pixels ); + assert( srcImage.format == destImage.format ); + + using namespace TriangleFilter; + + // Allocate initial temporary space (1 scanline, accumulation rows, plus X and Y filters) + ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast( _aligned_malloc( sizeof(XMVECTOR) * srcImage.width, 16 ) ) ); + if ( !scanline ) + return E_OUTOFMEMORY; + + std::unique_ptr rowActive( new (std::nothrow) TriangleRow[ destImage.height ] ); + if ( !rowActive ) + return E_OUTOFMEMORY; + + TriangleRow * rowFree = nullptr; + + std::unique_ptr tfX; + HRESULT hr = _Create( srcImage.width, destImage.width, (filter & TEX_FILTER_WRAP_U) != 0, tfX ); + if ( FAILED(hr) ) + return hr; + + std::unique_ptr tfY; + hr = _Create( srcImage.height, destImage.height, (filter & TEX_FILTER_WRAP_V) != 0, tfY ); + if ( FAILED(hr) ) + return hr; + + XMVECTOR* row = scanline.get(); + +#ifdef _DEBUG + memset( row, 0xCD, sizeof(XMVECTOR)*srcImage.width ); +#endif + + auto xFromEnd = reinterpret_cast( reinterpret_cast( tfX.get() ) + tfX->sizeInBytes ); + auto yFromEnd = reinterpret_cast( reinterpret_cast( tfY.get() ) + tfY->sizeInBytes ); + + // Count times rows get written + for( FilterFrom* yFrom = tfY->from; yFrom < yFromEnd; ) + { + for ( size_t j = 0; j < yFrom->count; ++j ) + { + size_t v = yFrom->to[ j ].u; + assert( v < destImage.height ); + ++rowActive.get()[ v ].remaining; + } + + yFrom = reinterpret_cast( reinterpret_cast( yFrom ) + yFrom->sizeInBytes ); + } + + // Filter image + const uint8_t* pSrc = srcImage.pixels; + size_t rowPitch = srcImage.rowPitch; + const uint8_t* pEndSrc = pSrc + rowPitch * srcImage.height; + + uint8_t* pDest = destImage.pixels; + + for( FilterFrom* yFrom = tfY->from; yFrom < yFromEnd; ) + { + // Create accumulation rows as needed + for ( size_t j = 0; j < yFrom->count; ++j ) + { + size_t v = yFrom->to[ j ].u; + assert( v < destImage.height ); + TriangleRow* rowAcc = &rowActive.get()[ v ]; + + if ( !rowAcc->scanline ) + { + if ( rowFree ) + { + // Steal and reuse scanline from 'free row' list + assert( rowFree->scanline != 0 ); + rowAcc->scanline.reset( rowFree->scanline.release() ); + rowFree = rowFree->next; + } + else + { + rowAcc->scanline.reset( reinterpret_cast( _aligned_malloc( sizeof(XMVECTOR) * destImage.width, 16 ) ) ); + if ( !rowAcc->scanline ) + return E_OUTOFMEMORY; + } + + memset( rowAcc->scanline.get(), 0, sizeof(XMVECTOR) * destImage.width ); + } + } + + // Load source scanline + if ( (pSrc + rowPitch) > pEndSrc ) + return E_FAIL; + + if ( !_LoadScanlineLinear( row, srcImage.width, pSrc, rowPitch, srcImage.format, filter ) ) + return E_FAIL; + + pSrc += rowPitch; + + // Process row + size_t x = 0; + for( FilterFrom* xFrom = tfX->from; xFrom < xFromEnd; ++x ) + { + for ( size_t j = 0; j < yFrom->count; ++j ) + { + size_t v = yFrom->to[ j ].u; + assert( v < destImage.height ); + float yweight = yFrom->to[ j ].weight; + + XMVECTOR* accPtr = rowActive[ v ].scanline.get(); + if ( !accPtr ) + return E_POINTER; + + for ( size_t k = 0; k < xFrom->count; ++k ) + { + size_t u = xFrom->to[ k ].u; + assert( u < destImage.width ); + + XMVECTOR weight = XMVectorReplicate( yweight * xFrom->to[ k ].weight ); + + assert( x < srcImage.width ); + accPtr[ u ] = XMVectorMultiplyAdd( row[ x ], weight, accPtr[ u ] ); + } + } + + xFrom = reinterpret_cast( reinterpret_cast( xFrom ) + xFrom->sizeInBytes ); + } + + // Write completed accumulation rows + for ( size_t j = 0; j < yFrom->count; ++j ) + { + size_t v = yFrom->to[ j ].u; + assert( v < destImage.height ); + TriangleRow* rowAcc = &rowActive.get()[ v ]; + + assert( rowAcc->remaining > 0 ); + --rowAcc->remaining; + + if ( !rowAcc->remaining ) + { + if ( !_StoreScanlineLinear( pDest + (destImage.rowPitch * v), destImage.rowPitch, destImage.format, rowAcc->scanline.get(), destImage.width, filter ) ) + return E_FAIL; + + // Put row on freelist to reuse it's allocated scanline + rowAcc->next = rowFree; + rowFree = rowAcc; + } + } + + yFrom = reinterpret_cast( reinterpret_cast( yFrom ) + yFrom->sizeInBytes ); + } + + return S_OK; +} + + //--- Custom filter resize --- static HRESULT _PerformResizeUsingCustomFilters( _In_ const Image& srcImage, _In_ DWORD filter, _In_ const Image& destImage ) { @@ -612,6 +769,9 @@ static HRESULT _PerformResizeUsingCustomFilters( _In_ const Image& srcImage, _In case TEX_FILTER_CUBIC: return _ResizeCubicFilter( srcImage, filter, destImage ); + case TEX_FILTER_TRIANGLE: + return _ResizeTriangleFilter( srcImage, filter, destImage ); + default: return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } diff --git a/DirectXTex/Filters.h b/DirectXTex/Filters.h index bf3005a..fa69e75 100644 --- a/DirectXTex/Filters.h +++ b/DirectXTex/Filters.h @@ -22,6 +22,10 @@ #include #endif +#include + +#include "scoped.h" + namespace DirectX { @@ -163,7 +167,7 @@ struct CubicFilter float x; }; -inline void _CreateCubicFilter( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, bool mirror, _Out_writes_(dest) CubicFilter* cf ) +inline void _CreateCubicFilter( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, _In_ bool mirror, _Out_writes_(dest) CubicFilter* cf ) { assert( source > 0 ); assert( dest > 0 ); @@ -206,4 +210,219 @@ inline void _CreateCubicFilter( _In_ size_t source, _In_ size_t dest, _In_ bool res = a0 + a1*vdx + a2*vdx2 + a3*vdx3; \ } + +//------------------------------------------------------------------------------------- +// Triangle filtering helpers +//------------------------------------------------------------------------------------- + +namespace TriangleFilter +{ + struct FilterTo + { + size_t u; + float weight; + }; + + struct FilterFrom + { + size_t count; + size_t sizeInBytes; + FilterTo to[1]; // variable-sized array + }; + + struct Filter + { + size_t sizeInBytes; + size_t totalSize; + FilterFrom from[1]; // variable-sized array + }; + + struct TriangleRow + { + size_t remaining; + TriangleRow* next; + ScopedAlignedArrayXMVECTOR scanline; + + TriangleRow() : remaining(0), next(nullptr) {} + }; + + static const size_t TF_FILTER_SIZE = sizeof(Filter) - sizeof(FilterFrom); + static const size_t TF_FROM_SIZE = sizeof(FilterFrom) - sizeof(FilterTo); + static const size_t TF_TO_SIZE = sizeof(FilterTo); + + static const float TF_EPSILON = 0.00001f; + + inline HRESULT _Create( _In_ size_t source, _In_ size_t dest, _In_ bool wrap, _Inout_ std::unique_ptr& tf ) + { + assert( source > 0 ); + assert( dest > 0 ); + + float scale = float(dest) / float(source); + float scaleInv = 0.5f / scale; + + // Determine storage required for filter and allocate memory if needed + size_t totalSize = TF_FILTER_SIZE + TF_FROM_SIZE + TF_TO_SIZE; + float repeat = (wrap) ? 1.f : 0.f; + + for( size_t u = 0; u < source; ++u ) + { + float src = float(u) - 0.5f; + float destMin = src * scale; + float destMax = destMin + scale; + + totalSize += TF_FROM_SIZE + TF_TO_SIZE + size_t( destMax - destMin + repeat + 1.f ) * TF_TO_SIZE * 2; + } + + uint8_t* pFilter = nullptr; + + if ( tf ) + { + // See if existing filter memory block is large enough to reuse + if ( tf->totalSize >= totalSize ) + { + pFilter = reinterpret_cast( tf.get() ); + } + else + { + // Need to reallocate filter memory block + tf.reset( nullptr ); + } + } + + if ( !tf ) + { + // Allocate filter memory block + pFilter = new (std::nothrow) uint8_t[ totalSize ]; + if ( !pFilter ) + return E_OUTOFMEMORY; + + tf.reset( reinterpret_cast( pFilter ) ); + tf->totalSize = totalSize; + } + + assert( pFilter != 0 ); + + // Filter setup + size_t sizeInBytes = TF_FILTER_SIZE; + size_t accumU = 0; + float accumWeight = 0.f; + + for( size_t u = 0; u < source; ++u ) + { + // Setup from entry + size_t sizeFrom = sizeInBytes; + auto pFrom = reinterpret_cast( pFilter + sizeInBytes ); + sizeInBytes += TF_FROM_SIZE; + + if ( sizeInBytes > totalSize ) + return E_FAIL; + + size_t toCount = 0; + + // Perform two passes to capture the influences from both sides + for( size_t j = 0; j < 2; ++j ) + { + float src = float( u + j ) - 0.5f; + + float destMin = src * scale; + float destMax = destMin + scale; + + if ( !wrap ) + { + // Clamp + if ( destMin < 0.f ) + destMin = 0.f; + if ( destMax > float(dest) ) + destMax = float(dest); + } + + for( auto k = static_cast( floorf( destMin ) ); float(k) < destMax; ++k ) + { + float d0 = float(k); + float d1 = d0 + 1.f; + + size_t u0; + if ( k < 0 ) + { + // Handle wrap + u0 = size_t( k + ptrdiff_t(dest) ); + } + else if ( k >= ptrdiff_t(dest) ) + { + // Handle wrap + u0 = size_t( k - ptrdiff_t(dest) ); + } + else + { + u0 = size_t( k ); + } + + // Save previous accumulated weight (if any) + if ( u0 != accumU ) + { + if ( accumWeight > TF_EPSILON ) + { + auto pTo = reinterpret_cast( pFilter + sizeInBytes ); + sizeInBytes += TF_TO_SIZE; + ++toCount; + + if ( sizeInBytes > totalSize ) + return E_FAIL; + + pTo->u = accumU; + pTo->weight = accumWeight; + } + + accumWeight = 0.f; + accumU = u0; + } + + // Clip destination + if ( d0 < destMin ) + d0 = destMin; + if ( d1 > destMax ) + d1 = destMax; + + // Calculate average weight over destination pixel + + float weight; + if ( !wrap && src < 0.f ) + weight = 1.f; + else if ( !wrap && ( ( src + 1.f ) >= float(source) ) ) + weight = 0.f; + else + weight = (d0 + d1) * scaleInv - src; + + accumWeight += (d1 - d0) * ( j ? (1.f - weight) : weight ); + } + } + + // Store accumulated weight + if ( accumWeight > TF_EPSILON ) + { + auto pTo = reinterpret_cast( pFilter + sizeInBytes ); + sizeInBytes += TF_TO_SIZE; + ++toCount; + + if ( sizeInBytes > totalSize ) + return E_FAIL; + + pTo->u = accumU; + pTo->weight = accumWeight; + } + + accumWeight = 0.f; + + // Finalize from entry + pFrom->count = toCount; + pFrom->sizeInBytes = sizeInBytes - sizeFrom; + } + + tf->sizeInBytes = sizeInBytes; + + return S_OK; + } + +}; // namespace + }; // namespace \ No newline at end of file diff --git a/Texconv/texconv.cpp b/Texconv/texconv.cpp index d2fc63f..3dd4c44 100644 --- a/Texconv/texconv.cpp +++ b/Texconv/texconv.cpp @@ -183,16 +183,19 @@ SValue g_pFilters[] = { L"CUBIC", TEX_FILTER_CUBIC }, { L"FANT", TEX_FILTER_FANT }, { L"BOX", TEX_FILTER_BOX }, + { L"TRIANGLE", TEX_FILTER_TRIANGLE }, { L"POINT_DITHER", TEX_FILTER_POINT | TEX_FILTER_DITHER }, { L"LINEAR_DITHER", TEX_FILTER_LINEAR | TEX_FILTER_DITHER }, { L"CUBIC_DITHER", TEX_FILTER_CUBIC | TEX_FILTER_DITHER }, { L"FANT_DITHER", TEX_FILTER_FANT | TEX_FILTER_DITHER }, { L"BOX_DITHER", TEX_FILTER_BOX | TEX_FILTER_DITHER }, + { L"TRIANGLE_DITHER", TEX_FILTER_TRIANGLE | TEX_FILTER_DITHER }, { L"POINT_DITHER_DIFFUSION", TEX_FILTER_POINT | TEX_FILTER_DITHER_DIFFUSION }, { L"LINEAR_DITHER_DIFFUSION", TEX_FILTER_LINEAR | TEX_FILTER_DITHER_DIFFUSION }, { L"CUBIC_DITHER_DIFFUSION", TEX_FILTER_CUBIC | TEX_FILTER_DITHER_DIFFUSION }, { L"FANT_DITHER_DIFFUSION", TEX_FILTER_FANT | TEX_FILTER_DITHER_DIFFUSION }, { L"BOX_DITHER_DIFFUSION", TEX_FILTER_BOX | TEX_FILTER_DITHER_DIFFUSION }, + { L"TRIANGLE_DITHER_DIFFUSION", TEX_FILTER_TRIANGLE | TEX_FILTER_DITHER_DIFFUSION }, { nullptr, TEX_FILTER_DEFAULT } };