]> git.cworth.org Git - apitrace/blob - thirdparty/directxtex/DirectXTex/DirectXTexNormalMaps.cpp
thirdparty/directxtex: Import DirectXTex library.
[apitrace] / thirdparty / directxtex / DirectXTex / DirectXTexNormalMaps.cpp
1 //-------------------------------------------------------------------------------------
2 // DirectXTexNormalMaps.cpp
3 //  
4 // DirectX Texture Library - Normal map operations
5 //
6 // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
7 // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
8 // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
9 // PARTICULAR PURPOSE.
10 //
11 // Copyright (c) Microsoft Corporation. All rights reserved.
12 //
13 // http://go.microsoft.com/fwlink/?LinkId=248926
14 //-------------------------------------------------------------------------------------
15
16 #include "directxtexp.h"
17
18 namespace DirectX
19 {
20
21 #pragma prefast(suppress : 25000, "FXMVECTOR is 16 bytes")
22 static inline float _EvaluateColor( _In_ FXMVECTOR val, _In_ DWORD flags )
23 {
24     XMFLOAT4A f;
25
26     static XMVECTORF32 lScale = { 0.2125f, 0.7154f, 0.0721f, 1.f };
27
28     static_assert( CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask" );
29     switch( flags & 0xf )
30     {
31     case 0:
32     case CNMAP_CHANNEL_RED:     return XMVectorGetX( val );
33     case CNMAP_CHANNEL_GREEN:   return XMVectorGetY( val );
34     case CNMAP_CHANNEL_BLUE:    return XMVectorGetZ( val );
35     case CNMAP_CHANNEL_ALPHA:   return XMVectorGetW( val );
36
37     case CNMAP_CHANNEL_LUMINANCE:
38         {
39             XMVECTOR v = XMVectorMultiply( val, lScale );
40             XMStoreFloat4A( &f, v );
41             return f.x + f.y + f.z;
42         }
43         break;
44
45     default:
46         assert(false);
47         return 0.f;
48     }
49 }
50
51 static void _EvaluateRow( _In_count_(width) const XMVECTOR* pSource, _Out_cap_(width+2) float* pDest,
52                           _In_ size_t width, _In_ DWORD flags )
53 {
54     assert( pSource && pDest );
55     assert( width > 0 );
56
57     for( size_t x = 0; x < width; ++x )
58     {
59         pDest[x+1] = _EvaluateColor( pSource[x], flags );
60     }
61
62     if ( flags & CNMAP_MIRROR_U )
63     {
64         // Mirror in U
65         pDest[0] = _EvaluateColor( pSource[0], flags );
66         pDest[width+1] = _EvaluateColor( pSource[width-1], flags );
67     }
68     else
69     {
70         // Wrap in U
71         pDest[0] = _EvaluateColor( pSource[width-1], flags );
72         pDest[width+1] = _EvaluateColor( pSource[0], flags );
73     }
74 }
75
76 static HRESULT _ComputeNMap( _In_ const Image& srcImage, _In_ DWORD flags, _In_ float amplitude,
77                              _In_ DXGI_FORMAT format, _In_ const Image& normalMap )
78 {
79     if ( !srcImage.pixels || !normalMap.pixels )
80         return E_INVALIDARG;
81
82     assert( !IsCompressed(format) && !IsTypeless( format ) );
83
84     const DWORD convFlags = _GetConvertFlags( format );
85     if ( !convFlags )
86         return E_FAIL;
87
88     if ( !( convFlags & (CONVF_UNORM | CONVF_SNORM | CONVF_FLOAT) ) )
89         HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
90
91     const size_t width = srcImage.width;
92     const size_t height = srcImage.height;
93     if ( width != normalMap.width || height != normalMap.height )
94         return E_FAIL;
95
96     // Allocate temporary space (4 scanlines and 3 evaluated rows)
97     ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast<XMVECTOR*>( _aligned_malloc( (sizeof(XMVECTOR)*width*4), 16 ) ) );
98     if ( !scanline )
99         return E_OUTOFMEMORY;
100
101     ScopedAlignedArrayFloat buffer( reinterpret_cast<float*>( _aligned_malloc( ( ( sizeof(float) * ( width + 2 ) ) * 3 ), 16 ) ) );
102     if ( !buffer )
103         return E_OUTOFMEMORY;
104
105     uint8_t* pDest = normalMap.pixels;
106     if ( !pDest )
107         return E_POINTER;
108
109     XMVECTOR* row0 = scanline.get();
110     XMVECTOR* row1 = row0 + width;
111     XMVECTOR* row2 = row1 + width;
112     XMVECTOR* target = row2 + width;
113
114     float* val0 = buffer.get();
115     float* val1 = val0 + width + 2;
116     float* val2 = val1 + width + 2;
117
118     const size_t rowPitch = srcImage.rowPitch;
119     const uint8_t* pSrc = srcImage.pixels;
120
121     // Read first scanline row into 'row1'
122     if ( !_LoadScanline( row1, width, pSrc, rowPitch, srcImage.format ) )
123         return E_FAIL;
124
125     // Setup 'row0'
126     if ( flags & CNMAP_MIRROR_V )
127     {
128         // Mirror first row
129         memcpy_s( row0, rowPitch, row1, rowPitch );
130     }
131     else
132     {
133         // Read last row (Wrap V)
134         if ( !_LoadScanline( row0, width, pSrc + (rowPitch * (height-1)), rowPitch, srcImage.format ) )
135             return E_FAIL;
136     }
137
138     // Evaluate the initial rows
139     _EvaluateRow( row0, val0, width, flags );
140     _EvaluateRow( row1, val1, width, flags );
141
142     pSrc += rowPitch;
143
144     for( size_t y = 0; y < height; ++y )
145     {
146         // Load next scanline of source image
147         if ( y < (height-1) )
148         {
149             if ( !_LoadScanline( row2, width, pSrc, rowPitch, srcImage.format ) )
150                 return E_FAIL;
151         }
152         else
153         {
154             if ( flags & CNMAP_MIRROR_V )
155             {
156                 // Use last row of source image
157                 if ( !_LoadScanline( row2, width, srcImage.pixels + (rowPitch * (height-1)), rowPitch, srcImage.format ) )
158                     return E_FAIL;
159             }
160             else
161             {
162                 // Use first row of source image (Wrap V)
163                 if ( !_LoadScanline( row2, width, srcImage.pixels, rowPitch, srcImage.format ) )
164                     return E_FAIL;
165             }
166         }
167
168         // Evaluate row
169         _EvaluateRow( row2, val2, width, flags );
170
171         // Generate target scanline
172         XMVECTOR *dptr = target;
173         for( size_t x = 0; x < width; ++x )
174         {
175             // Compute normal via central differencing
176             float totDelta = ( val0[x] - val0[x+2] ) + ( val1[x] - val1[x+2] ) + ( val2[x] - val2[x+2] );
177             float deltaZX = totDelta * amplitude / 6.f;
178
179             totDelta = ( val0[x] - val2[x] ) + ( val0[x+1] - val2[x+1] ) + ( val0[x+2] - val2[x+2] );
180             float deltaZY = totDelta * amplitude / 6.f;
181
182             XMVECTOR vx = XMVectorSetZ( g_XMNegIdentityR0, deltaZX );   // (-1.0f, 0.0f, deltaZX)
183             XMVECTOR vy = XMVectorSetZ( g_XMNegIdentityR1, deltaZY );   // (0.0f, -1.0f, deltaZY)
184
185             XMVECTOR normal = XMVector3Normalize( XMVector3Cross( vx, vy ) );
186
187             // Compute alpha (1.0 or an occlusion term)
188             float alpha = 1.f;
189
190             if ( flags & CNMAP_COMPUTE_OCCLUSION )
191             {
192                 float delta = 0.f;
193                 float c = val1[x+1];
194
195                 float t = val0[x] - c;  if ( t > 0.f ) delta += t;
196                 t = val0[x+1]   - c;    if ( t > 0.f ) delta += t;
197                 t = val0[x+2]   - c;    if ( t > 0.f ) delta += t;
198                 t = val1[x]     - c;    if ( t > 0.f ) delta += t;
199                 // Skip current pixel
200                 t = val1[x+2]   - c;    if ( t > 0.f ) delta += t;
201                 t = val2[x]     - c;    if ( t > 0.f ) delta += t;
202                 t = val2[x+1]   - c;    if ( t > 0.f ) delta += t;
203                 t = val2[x+2]   - c;    if ( t > 0.f ) delta += t;
204
205                 // Average delta (divide by 8, scale by amplitude factor)
206                 delta *= 0.125f * amplitude;
207                 if ( delta > 0.f )
208                 {
209                     // If < 0, then no occlusion
210                     float r = sqrtf( 1.f + delta*delta );
211                     alpha = (r - delta) / r;
212                 }
213             }
214
215             // Encode based on target format
216             if ( convFlags & CONVF_UNORM )
217             {
218                 // 0.5f*normal + 0.5f -or- invert sign case: -0.5f*normal + 0.5f
219                 XMVECTOR n1 = XMVectorMultiplyAdd( (flags & CNMAP_INVERT_SIGN) ? g_XMNegativeOneHalf : g_XMOneHalf, normal, g_XMOneHalf ); 
220                 *dptr++ = XMVectorSetW( n1, alpha );
221             }
222             else if ( flags & CNMAP_INVERT_SIGN )
223             {
224                 *dptr++ = XMVectorSetW( XMVectorNegate( normal ), alpha );
225             }
226             else
227             {
228                 *dptr++ = XMVectorSetW( normal, alpha );
229             }
230         }
231
232         if ( !_StoreScanline( pDest, normalMap.rowPitch, format, target, width ) )
233             return E_FAIL;
234
235         // Cycle buffers
236         float* temp = val0;
237         val0 = val1;
238         val1 = val2;
239         val2 = temp;
240
241         pSrc += rowPitch;
242         pDest += normalMap.rowPitch;
243     }
244
245     return S_OK;
246 }
247
248
249 //=====================================================================================
250 // Entry points
251 //=====================================================================================
252         
253 //-------------------------------------------------------------------------------------
254 // Generates a normal map from a height-map
255 //-------------------------------------------------------------------------------------
256 HRESULT ComputeNormalMap( const Image& srcImage, DWORD flags, float amplitude,
257                           DXGI_FORMAT format, ScratchImage& normalMap )
258 {
259     if ( !srcImage.pixels || !IsValid(format) || IsCompressed( format ) || IsTypeless( format ) )
260         return E_INVALIDARG;
261
262     static_assert( CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask" );
263     switch( flags & 0xf )
264     {
265     case 0:
266     case CNMAP_CHANNEL_RED:
267     case CNMAP_CHANNEL_GREEN:
268     case CNMAP_CHANNEL_BLUE:
269     case CNMAP_CHANNEL_ALPHA:
270     case CNMAP_CHANNEL_LUMINANCE:
271         break;
272
273     default:
274         return E_INVALIDARG;
275     }
276
277     if ( IsCompressed( srcImage.format ) || IsTypeless( srcImage.format ) )
278         return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
279
280     // Setup target image
281     normalMap.Release();
282
283     HRESULT hr = normalMap.Initialize2D( format, srcImage.width, srcImage.height, 1, 1 );
284     if ( FAILED(hr) )
285         return hr;
286
287     const Image *img = normalMap.GetImage( 0, 0, 0 );
288     if ( !img )
289     {
290         normalMap.Release();
291         return E_POINTER;
292     }
293
294     hr = _ComputeNMap( srcImage, flags, amplitude, format, *img );
295     if ( FAILED(hr) )
296     {
297         normalMap.Release();
298         return hr;
299     }
300
301     return S_OK;
302 }
303
304 HRESULT ComputeNormalMap( const Image* srcImages, size_t nimages, const TexMetadata& metadata,
305                           DWORD flags, float amplitude, DXGI_FORMAT format, ScratchImage& normalMaps )
306 {
307     if ( !srcImages || !nimages )
308         return E_INVALIDARG;
309
310     if ( !IsValid(format) || IsCompressed(format) || IsTypeless(format) )
311         return E_INVALIDARG;
312
313     static_assert( CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask" );
314     switch( flags & 0xf )
315     {
316     case 0:
317     case CNMAP_CHANNEL_RED:
318     case CNMAP_CHANNEL_GREEN:
319     case CNMAP_CHANNEL_BLUE:
320     case CNMAP_CHANNEL_ALPHA:
321     case CNMAP_CHANNEL_LUMINANCE:
322         break;
323
324     default:
325         return E_INVALIDARG;
326     }
327
328     normalMaps.Release();
329
330     TexMetadata mdata2 = metadata;
331     mdata2.format = format;
332     HRESULT hr = normalMaps.Initialize( mdata2 );
333     if ( FAILED(hr) )
334         return hr;
335
336     if ( nimages != normalMaps.GetImageCount() )
337     {
338         normalMaps.Release();
339         return E_FAIL;
340     }
341
342     const Image* dest = normalMaps.GetImages();
343     if ( !dest )
344     {
345         normalMaps.Release();
346         return E_POINTER;
347     }
348
349     for( size_t index=0; index < nimages; ++index )
350     {
351         assert( dest[ index ].format == format );
352
353         const Image& src = srcImages[ index ];
354         if ( IsCompressed( src.format ) || IsTypeless( src.format ) )
355         {
356             normalMaps.Release();
357             return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
358         }
359
360         if ( src.width != dest[ index ].width || src.height != dest[ index ].height )
361         {
362             normalMaps.Release();
363             return E_FAIL;
364         }
365
366         hr = _ComputeNMap( src, flags, amplitude, format, dest[ index ] );
367         if ( FAILED(hr) )
368         {
369             normalMaps.Release();
370             return hr;
371         }
372     }
373
374     return S_OK;
375 }
376
377 }; // namespace