Merge "Use Mipmap level to sample specular texture" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-scene3d / public-api / loader / material-definition.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-scene3d/public-api/loader/material-definition.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali-toolkit/devel-api/builder/base64-encoding.h>
23 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
24 #include <dali/devel-api/adaptor-framework/image-loading.h>
25
26 namespace Dali
27 {
28 using namespace Toolkit;
29
30 namespace Scene3D
31 {
32 namespace Loader
33 {
34 namespace
35 {
36 constexpr SamplerFlags::Type FILTER_MODES_FROM_DALI[]{
37   SamplerFlags::FILTER_LINEAR | SamplerFlags::FILTER_MIPMAP_NEAREST,
38   SamplerFlags::FILTER_LINEAR,
39   SamplerFlags::FILTER_NEAREST,
40   SamplerFlags::FILTER_LINEAR,
41   SamplerFlags::FILTER_NEAREST | SamplerFlags::FILTER_MIPMAP_NEAREST,
42   SamplerFlags::FILTER_LINEAR | SamplerFlags::FILTER_MIPMAP_NEAREST,
43   SamplerFlags::FILTER_NEAREST | SamplerFlags::FILTER_MIPMAP_LINEAR,
44   SamplerFlags::FILTER_LINEAR | SamplerFlags::FILTER_MIPMAP_LINEAR,
45 };
46
47 constexpr SamplerFlags::Type WRAP_MODES_FROM_DALI[]{
48   SamplerFlags::WRAP_CLAMP,
49   SamplerFlags::WRAP_CLAMP,
50   SamplerFlags::WRAP_REPEAT,
51   SamplerFlags::WRAP_MIRROR,
52 };
53
54 constexpr FilterMode::Type FILTER_MODES_TO_DALI[]{
55   FilterMode::NEAREST,
56   FilterMode::LINEAR,
57   FilterMode::NEAREST_MIPMAP_NEAREST,
58   FilterMode::LINEAR_MIPMAP_NEAREST,
59   FilterMode::NEAREST_MIPMAP_LINEAR,
60   FilterMode::LINEAR_MIPMAP_LINEAR,
61 };
62
63 constexpr WrapMode::Type WRAP_MODES_TO_DALI[]{
64   WrapMode::REPEAT,
65   WrapMode::CLAMP_TO_EDGE,
66   WrapMode::MIRRORED_REPEAT};
67
68 const SamplerFlags::Type GetSingleValueSampler()
69 {
70   static const SamplerFlags::Type SINGLE_VALUE_SAMPLER = SamplerFlags::Encode(FilterMode::NEAREST, FilterMode::NEAREST, WrapMode::CLAMP_TO_EDGE, WrapMode::CLAMP_TO_EDGE);
71   return SINGLE_VALUE_SAMPLER;
72 }
73
74 static constexpr std::string_view EMBEDDED_DATA_PREFIX               = "data:";
75 static constexpr std::string_view EMBEDDED_DATA_IMAGE_MEDIA_TYPE     = "image/";
76 static constexpr std::string_view EMBEDDED_DATA_BASE64_ENCODING_TYPE = "base64,";
77
78 Dali::PixelData LoadImageResource(const std::string& resourcePath,
79                                   TextureDefinition& textureDefinition,
80                                   FittingMode::Type  fittingMode,
81                                   bool               orientationCorrection)
82 {
83   Dali::PixelData pixelData;
84   if(!textureDefinition.mTextureBuffer.empty())
85   {
86     Dali::Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromBuffer(textureDefinition.mTextureBuffer.data(), textureDefinition.mTextureBuffer.size(), textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
87     if(pixelBuffer)
88     {
89       pixelData = Devel::PixelBuffer::Convert(pixelBuffer);
90     }
91   }
92   else if(textureDefinition.mImageUri.find(EMBEDDED_DATA_PREFIX.data()) == 0 && textureDefinition.mImageUri.find(EMBEDDED_DATA_IMAGE_MEDIA_TYPE.data(), EMBEDDED_DATA_PREFIX.length()) == EMBEDDED_DATA_PREFIX.length())
93   {
94     uint32_t position = textureDefinition.mImageUri.find(EMBEDDED_DATA_BASE64_ENCODING_TYPE.data(), EMBEDDED_DATA_PREFIX.length() + EMBEDDED_DATA_IMAGE_MEDIA_TYPE.length());
95     if(position != std::string::npos)
96     {
97       position += EMBEDDED_DATA_BASE64_ENCODING_TYPE.length();
98       std::string_view     data = std::string_view(textureDefinition.mImageUri).substr(position);
99       std::vector<uint8_t> buffer;
100       Dali::Toolkit::DecodeBase64FromString(data, buffer);
101       uint32_t bufferSize = buffer.size();
102
103       Dali::Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromBuffer(reinterpret_cast<uint8_t*>(buffer.data()), bufferSize, textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
104       if(pixelBuffer)
105       {
106         pixelData = Devel::PixelBuffer::Convert(pixelBuffer);
107       }
108     }
109   }
110   else
111   {
112     pixelData = SyncImageLoader::Load(resourcePath + textureDefinition.mImageUri, textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
113   }
114   return pixelData;
115 }
116 } // namespace
117
118 SamplerFlags::Type SamplerFlags::Encode(FilterMode::Type minFilter, FilterMode::Type magFilter, WrapMode::Type wrapS, WrapMode::Type wrapT)
119 {
120   return FILTER_MODES_FROM_DALI[minFilter] | ((FILTER_MODES_FROM_DALI[magFilter] & FILTER_MAG_BITS) << FILTER_MAG_SHIFT) |
121          (WRAP_MODES_FROM_DALI[wrapS] << WRAP_S_SHIFT) | (WRAP_MODES_FROM_DALI[wrapT] << WRAP_T_SHIFT);
122 }
123
124 FilterMode::Type SamplerFlags::GetMinFilter(Type flags)
125 {
126   return FILTER_MODES_TO_DALI[flags & FILTER_MIN_MASK];
127 }
128
129 FilterMode::Type SamplerFlags::GetMagFilter(Type flags)
130 {
131   return FILTER_MODES_TO_DALI[(flags >> FILTER_MAG_SHIFT) & FILTER_MAG_MASK];
132 }
133
134 WrapMode::Type SamplerFlags::GetWrapS(Type flags)
135 {
136   return WRAP_MODES_TO_DALI[(flags >> WRAP_S_SHIFT) & WRAP_S_MASK];
137 }
138
139 WrapMode::Type SamplerFlags::GetWrapT(Type flags)
140 {
141   return WRAP_MODES_TO_DALI[(flags >> WRAP_T_SHIFT) & WRAP_T_MASK];
142 }
143
144 Sampler SamplerFlags::MakeSampler(Type flags)
145 {
146   auto sampler = Sampler::New();
147   sampler.SetFilterMode(GetMinFilter(flags), GetMagFilter(flags));
148   sampler.SetWrapMode(GetWrapS(flags), GetWrapT(flags));
149   return sampler;
150 }
151
152 TextureDefinition::TextureDefinition(const std::string& imageUri, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
153 : mImageUri(imageUri),
154   mSamplerFlags(samplerFlags),
155   mMinImageDimensions(minImageDimensions),
156   mSamplingMode(samplingMode)
157 {
158 }
159
160 TextureDefinition::TextureDefinition(std::string&& imageUri, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
161 : mImageUri(std::move(imageUri)),
162   mSamplerFlags(samplerFlags),
163   mMinImageDimensions(minImageDimensions),
164   mSamplingMode(samplingMode)
165 {
166 }
167
168 TextureDefinition::TextureDefinition(std::vector<uint8_t>&& textureBuffer, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
169 : mImageUri(),
170   mSamplerFlags(samplerFlags),
171   mMinImageDimensions(minImageDimensions),
172   mSamplingMode(samplingMode),
173   mTextureBuffer(std::move(textureBuffer))
174 {
175 }
176
177 MaterialDefinition::RawData
178 MaterialDefinition::LoadRaw(const std::string& imagesPath)
179 {
180   RawData raw;
181
182   const bool hasTransparency = MaskMatch(mFlags, TRANSPARENCY);
183   // Why we add additional count here?
184   uint32_t numBuffers = static_cast<uint32_t>(mTextureStages.size()) + (hasTransparency ? !CheckTextures(ALBEDO) + !CheckTextures(METALLIC | ROUGHNESS) + !CheckTextures(NORMAL)
185                                                                                         : !CheckTextures(ALBEDO | METALLIC) + !CheckTextures(NORMAL | ROUGHNESS));
186   if(numBuffers == 0)
187   {
188     return raw;
189   }
190   raw.mTextures.reserve(numBuffers);
191
192   // Load textures
193   auto iTexture   = mTextureStages.begin();
194   auto checkStage = [&](uint32_t flags) {
195     return iTexture != mTextureStages.end() && MaskMatch(iTexture->mSemantic, flags);
196   };
197
198   // Check for compulsory textures: Albedo, Metallic, Roughness, Normal
199   if(checkStage(ALBEDO | METALLIC))
200   {
201     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
202     ++iTexture;
203
204     if(checkStage(NORMAL | ROUGHNESS))
205     {
206       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
207       ++iTexture;
208     }
209     else // single value normal-roughness
210     {
211       const auto bufferSize = 4;
212       uint8_t*   buffer     = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff, 0xff}; // normal of (0, 0, 1), roughness of 1
213       raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
214     }
215   }
216   else
217   {
218     if(checkStage(ALBEDO))
219     {
220       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
221       ++iTexture;
222     }
223     else if(mNeedAlbedoTexture) // single value albedo, albedo-alpha or albedo-metallic
224     {
225       uint32_t bufferSize = 4;
226       uint8_t* buffer     = nullptr;
227       auto     format     = Pixel::Format::RGBA8888;
228       if(hasTransparency) // albedo-alpha
229       {
230         buffer    = new uint8_t[bufferSize];
231         buffer[3] = static_cast<uint8_t>(mColor.a * 255.f);
232       }
233       else if(!checkStage(METALLIC | ROUGHNESS)) // albedo-metallic
234       {
235         buffer    = new uint8_t[bufferSize];
236         buffer[3] = 0xff; // metallic of 1.0
237       }
238       else // albedo
239       {
240         bufferSize = 3;
241         buffer     = new uint8_t[bufferSize];
242         format     = Pixel::Format::RGB888;
243       }
244       buffer[0] = static_cast<uint8_t>(mColor.r * 255.f);
245       buffer[1] = static_cast<uint8_t>(mColor.g * 255.f);
246       buffer[2] = static_cast<uint8_t>(mColor.b * 255.f);
247       raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, format, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
248     }
249
250     // If we have transparency, or an image based albedo map, we will have to continue with separate metallicRoughness + normal.
251     const bool createMetallicRoughnessAndNormal = hasTransparency || std::distance(mTextureStages.begin(), iTexture) > 0;
252     if(checkStage(METALLIC | ROUGHNESS))
253     {
254       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
255       ++iTexture;
256     }
257     else if(createMetallicRoughnessAndNormal && mNeedMetallicRoughnessTexture)
258     {
259       // NOTE: we want to set both metallic and roughness to 1.0; dli uses the R & A channels,
260       // glTF2 uses B & G, so we might as well just set all components to 1.0.
261       const auto bufferSize = 4;
262       uint8_t*   buffer     = new uint8_t[bufferSize]{0xff, 0xff, 0xff, 0xff};
263       raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
264     }
265
266     if(checkStage(NORMAL))
267     {
268       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
269       ++iTexture;
270     }
271     else if(mNeedNormalTexture)
272     {
273       if(createMetallicRoughnessAndNormal)
274       {
275         const auto bufferSize = 3;
276         uint8_t*   buffer     = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff}; // normal of (0, 0, 1)
277         raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGB888, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
278       }
279       else // single-value normal-roughness
280       {
281         const auto bufferSize = 4;
282         uint8_t*   buffer     = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff, 0xff}; // normal of (0, 0, 1), roughness of 1.0
283         raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
284       }
285     }
286   }
287
288   // Extra textures.
289   if(checkStage(SUBSURFACE))
290   {
291     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
292     ++iTexture;
293   }
294
295   if(checkStage(OCCLUSION))
296   {
297     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
298     ++iTexture;
299   }
300
301   if(checkStage(EMISSIVE))
302   {
303     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
304     ++iTexture;
305   }
306
307   if(checkStage(SPECULAR))
308   {
309     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
310     ++iTexture;
311   }
312
313   if(checkStage(SPECULAR_COLOR))
314   {
315     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
316     ++iTexture;
317   }
318
319   return raw;
320 }
321
322 TextureSet MaterialDefinition::Load(const EnvironmentDefinition::Vector& environments, RawData&& raw) const
323 {
324   auto textureSet = TextureSet::New();
325
326   uint32_t n = 0;
327   for(auto& tData : raw.mTextures)
328   {
329     auto&   pixels = tData.mPixels;
330     Texture texture;
331     if(pixels)
332     {
333       texture = Texture::New(TextureType::TEXTURE_2D, pixels.GetPixelFormat(), pixels.GetWidth(), pixels.GetHeight());
334       texture.Upload(tData.mPixels, 0, 0, 0, 0, pixels.GetWidth(), pixels.GetHeight());
335       if(tData.mSamplerFlags & SamplerFlags::MIPMAP_MASK)
336       {
337         texture.GenerateMipmaps();
338       }
339     }
340
341     textureSet.SetTexture(n, texture);
342     textureSet.SetSampler(n, SamplerFlags::MakeSampler(tData.mSamplerFlags));
343
344     ++n;
345   }
346
347   // Assign textures to slots -- starting with 2D ones, then cubemaps, if any.
348   if(mEnvironmentIdx < static_cast<Index>(environments.size()))
349   {
350     auto& envTextures = environments[mEnvironmentIdx].second;
351     // If pre-computed brdf texture is defined, set the texture.
352     if(envTextures.mBrdf)
353     {
354       textureSet.SetTexture(n, envTextures.mBrdf);
355       ++n;
356     }
357
358     if(envTextures.mDiffuse)
359     {
360       textureSet.SetTexture(n, envTextures.mDiffuse);
361       ++n;
362     }
363
364     if(envTextures.mSpecular)
365     {
366       auto specularSampler = Sampler::New();
367       specularSampler.SetWrapMode(WrapMode::CLAMP_TO_EDGE, WrapMode::CLAMP_TO_EDGE, WrapMode::CLAMP_TO_EDGE);
368       specularSampler.SetFilterMode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR);
369
370       textureSet.SetTexture(n, envTextures.mSpecular);
371       textureSet.SetSampler(n, specularSampler);
372       ++n;
373     }
374   }
375   else
376   {
377     ExceptionFlinger(ASSERT_LOCATION) << "Environment index (" << mEnvironmentIdx << ") out of bounds (" << environments.size() << ").";
378   }
379
380   return textureSet;
381 }
382
383 bool MaterialDefinition::CheckTextures(uint32_t flags) const
384 {
385   return std::find_if(mTextureStages.begin(), mTextureStages.end(), [flags](const TextureStage& ts) { return MaskMatch(ts.mSemantic, flags); }) != mTextureStages.end();
386 }
387
388 } // namespace Loader
389 } // namespace Scene3D
390 } // namespace Dali