bf764ff13b949dd9b28de22a518f2d28410219ed
[platform/core/uifw/dali-toolkit.git] / dali-scene3d / public-api / loader / material-definition.cpp
1 /*
2  * Copyright (c) 2022 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 // INTERNAL INCLUDES
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 SINGLE_VALUE_SAMPLER = SamplerFlags::Encode(FilterMode::NEAREST, FilterMode::NEAREST, WrapMode::CLAMP_TO_EDGE, WrapMode::CLAMP_TO_EDGE);
69
70 static constexpr std::string_view EMBEDDED_DATA_PREFIX               = "data:";
71 static constexpr std::string_view EMBEDDED_DATA_IMAGE_MEDIA_TYPE     = "image/";
72 static constexpr std::string_view EMBEDDED_DATA_BASE64_ENCODING_TYPE = "base64,";
73
74 Dali::PixelData LoadImageResource(const std::string& resourcePath,
75                                   TextureDefinition& textureDefinition,
76                                   FittingMode::Type  fittingMode,
77                                   bool               orientationCorrection)
78 {
79   Dali::PixelData pixelData;
80   if(!textureDefinition.mTextureBuffer.empty())
81   {
82     Dali::Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromBuffer(textureDefinition.mTextureBuffer.data(), textureDefinition.mTextureBuffer.size(), textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
83     if(pixelBuffer)
84     {
85       pixelData = Devel::PixelBuffer::Convert(pixelBuffer);
86     }
87   }
88   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())
89   {
90     uint32_t position = textureDefinition.mImageUri.find(EMBEDDED_DATA_BASE64_ENCODING_TYPE.data(), EMBEDDED_DATA_PREFIX.length() + EMBEDDED_DATA_IMAGE_MEDIA_TYPE.length());
91     if(position != std::string::npos)
92     {
93       position += EMBEDDED_DATA_BASE64_ENCODING_TYPE.length();
94       std::string          data = textureDefinition.mImageUri.substr(position);
95       std::vector<uint8_t> buffer;
96       Dali::Toolkit::DecodeBase64PropertyData(data, buffer);
97       uint32_t bufferSize = buffer.size();
98
99       Dali::Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromBuffer(reinterpret_cast<uint8_t*>(buffer.data()), bufferSize, textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
100       if(pixelBuffer)
101       {
102         pixelData = Devel::PixelBuffer::Convert(pixelBuffer);
103       }
104     }
105   }
106   else
107   {
108     pixelData = SyncImageLoader::Load(resourcePath + textureDefinition.mImageUri, textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
109   }
110   return pixelData;
111 }
112 } // namespace
113
114 SamplerFlags::Type SamplerFlags::Encode(FilterMode::Type minFilter, FilterMode::Type magFilter, WrapMode::Type wrapS, WrapMode::Type wrapT)
115 {
116   return FILTER_MODES_FROM_DALI[minFilter] | ((FILTER_MODES_FROM_DALI[magFilter] & FILTER_MAG_BITS) << FILTER_MAG_SHIFT) |
117          (WRAP_MODES_FROM_DALI[wrapS] << WRAP_S_SHIFT) | (WRAP_MODES_FROM_DALI[wrapT] << WRAP_T_SHIFT);
118 }
119
120 FilterMode::Type SamplerFlags::GetMinFilter(Type flags)
121 {
122   return FILTER_MODES_TO_DALI[flags & FILTER_MIN_MASK];
123 }
124
125 FilterMode::Type SamplerFlags::GetMagFilter(Type flags)
126 {
127   return FILTER_MODES_TO_DALI[(flags >> FILTER_MAG_SHIFT) & FILTER_MAG_MASK];
128 }
129
130 WrapMode::Type SamplerFlags::GetWrapS(Type flags)
131 {
132   return WRAP_MODES_TO_DALI[(flags >> WRAP_S_SHIFT) & WRAP_S_MASK];
133 }
134
135 WrapMode::Type SamplerFlags::GetWrapT(Type flags)
136 {
137   return WRAP_MODES_TO_DALI[(flags >> WRAP_T_SHIFT) & WRAP_T_MASK];
138 }
139
140 Sampler SamplerFlags::MakeSampler(Type flags)
141 {
142   auto sampler = Sampler::New();
143   sampler.SetFilterMode(GetMinFilter(flags), GetMagFilter(flags));
144   sampler.SetWrapMode(GetWrapS(flags), GetWrapT(flags));
145   return sampler;
146 }
147
148 TextureDefinition::TextureDefinition(const std::string& imageUri, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
149 : mImageUri(imageUri),
150   mSamplerFlags(samplerFlags),
151   mMinImageDimensions(minImageDimensions),
152   mSamplingMode(samplingMode)
153 {
154 }
155
156 TextureDefinition::TextureDefinition(std::string&& imageUri, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
157 : mImageUri(std::move(imageUri)),
158   mSamplerFlags(samplerFlags),
159   mMinImageDimensions(minImageDimensions),
160   mSamplingMode(samplingMode)
161 {
162 }
163
164 TextureDefinition::TextureDefinition(std::vector<uint8_t>&& textureBuffer, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
165 : mImageUri(),
166   mSamplerFlags(samplerFlags),
167   mMinImageDimensions(minImageDimensions),
168   mSamplingMode(samplingMode),
169   mTextureBuffer(std::move(textureBuffer))
170 {
171 }
172
173 MaterialDefinition::RawData
174 MaterialDefinition::LoadRaw(const std::string& imagesPath)
175 {
176   RawData raw;
177
178   const bool hasTransparency = MaskMatch(mFlags, TRANSPARENCY);
179   // Why we add additional count here?
180   uint32_t numBuffers = static_cast<uint32_t>(mTextureStages.size()) + (hasTransparency ? !CheckTextures(ALBEDO) + !CheckTextures(METALLIC | ROUGHNESS) + !CheckTextures(NORMAL)
181                                                                                         : !CheckTextures(ALBEDO | METALLIC) + !CheckTextures(NORMAL | ROUGHNESS));
182   if(numBuffers == 0)
183   {
184     return raw;
185   }
186   raw.mTextures.reserve(numBuffers);
187
188   // Load textures
189   auto iTexture   = mTextureStages.begin();
190   auto checkStage = [&](uint32_t flags)
191   {
192     return iTexture != mTextureStages.end() && MaskMatch(iTexture->mSemantic, flags);
193   };
194
195   // Check for compulsory textures: Albedo, Metallic, Roughness, Normal
196   if(checkStage(ALBEDO | METALLIC))
197   {
198     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
199     ++iTexture;
200
201     if(checkStage(NORMAL | ROUGHNESS))
202     {
203       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
204       ++iTexture;
205     }
206     else // single value normal-roughness
207     {
208       const auto bufferSize = 4;
209       uint8_t*   buffer     = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff, 0xff}; // normal of (0, 0, 1), roughness of 1
210       raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), SINGLE_VALUE_SAMPLER});
211     }
212   }
213   else
214   {
215     if(checkStage(ALBEDO))
216     {
217       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
218       ++iTexture;
219     }
220     else if(mNeedAlbedoTexture) // single value albedo, albedo-alpha or albedo-metallic
221     {
222       uint32_t bufferSize = 4;
223       uint8_t* buffer     = nullptr;
224       auto     format     = Pixel::Format::RGBA8888;
225       if(hasTransparency) // albedo-alpha
226       {
227         buffer    = new uint8_t[bufferSize];
228         buffer[3] = static_cast<uint8_t>(mColor.a * 255.f);
229       }
230       else if(!checkStage(METALLIC | ROUGHNESS)) // albedo-metallic
231       {
232         buffer    = new uint8_t[bufferSize];
233         buffer[3] = 0xff; // metallic of 1.0
234       }
235       else // albedo
236       {
237         bufferSize = 3;
238         buffer     = new uint8_t[bufferSize];
239         format     = Pixel::Format::RGB888;
240       }
241       buffer[0] = static_cast<uint8_t>(mColor.r * 255.f);
242       buffer[1] = static_cast<uint8_t>(mColor.g * 255.f);
243       buffer[2] = static_cast<uint8_t>(mColor.b * 255.f);
244       raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, format, PixelData::DELETE_ARRAY), SINGLE_VALUE_SAMPLER});
245     }
246
247     // If we have transparency, or an image based albedo map, we will have to continue with separate metallicRoughness + normal.
248     const bool createMetallicRoughnessAndNormal = hasTransparency || std::distance(mTextureStages.begin(), iTexture) > 0;
249     if(checkStage(METALLIC | ROUGHNESS))
250     {
251       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
252       ++iTexture;
253     }
254     else if(createMetallicRoughnessAndNormal && mNeedMetallicRoughnessTexture)
255     {
256       // NOTE: we want to set both metallic and roughness to 1.0; dli uses the R & A channels,
257       // glTF2 uses B & G, so we might as well just set all components to 1.0.
258       const auto bufferSize = 4;
259       uint8_t*   buffer     = new uint8_t[bufferSize]{0xff, 0xff, 0xff, 0xff};
260       raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), SINGLE_VALUE_SAMPLER});
261     }
262
263     if(checkStage(NORMAL))
264     {
265       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
266       ++iTexture;
267     }
268     else if(mNeedNormalTexture)
269     {
270       if(createMetallicRoughnessAndNormal)
271       {
272         const auto bufferSize = 3;
273         uint8_t*   buffer     = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff}; // normal of (0, 0, 1)
274         raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGB888, PixelData::DELETE_ARRAY), SINGLE_VALUE_SAMPLER});
275       }
276       else // single-value normal-roughness
277       {
278         const auto bufferSize = 4;
279         uint8_t*   buffer     = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff, 0xff}; // normal of (0, 0, 1), roughness of 1.0
280         raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), SINGLE_VALUE_SAMPLER});
281       }
282     }
283   }
284
285   // Extra textures.
286   if(checkStage(SUBSURFACE))
287   {
288     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
289     ++iTexture;
290   }
291
292   if(checkStage(OCCLUSION))
293   {
294     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
295     ++iTexture;
296   }
297
298   if(checkStage(EMISSIVE))
299   {
300     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
301     ++iTexture;
302   }
303
304   if(checkStage(SPECULAR))
305   {
306     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
307     ++iTexture;
308   }
309
310   if(checkStage(SPECULAR_COLOR))
311   {
312     raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
313     ++iTexture;
314   }
315
316   return raw;
317 }
318
319 TextureSet MaterialDefinition::Load(const EnvironmentDefinition::Vector& environments, RawData&& raw) const
320 {
321   auto textureSet = TextureSet::New();
322
323   uint32_t n = 0;
324   for(auto& tData : raw.mTextures)
325   {
326     auto& pixels  = tData.mPixels;
327     Texture texture;
328     if(pixels)
329     {
330       texture = Texture::New(TextureType::TEXTURE_2D, pixels.GetPixelFormat(), pixels.GetWidth(), pixels.GetHeight());
331       texture.Upload(tData.mPixels, 0, 0, 0, 0, pixels.GetWidth(), pixels.GetHeight());
332       if(tData.mSamplerFlags & SamplerFlags::MIPMAP_MASK)
333       {
334         texture.GenerateMipmaps();
335       }
336     }
337
338     textureSet.SetTexture(n, texture);
339     textureSet.SetSampler(n, SamplerFlags::MakeSampler(tData.mSamplerFlags));
340
341     ++n;
342   }
343
344   // Assign textures to slots -- starting with 2D ones, then cubemaps, if any.
345   if(mEnvironmentIdx < static_cast<Index>(environments.size()))
346   {
347     auto& envTextures = environments[mEnvironmentIdx].second;
348     // If pre-computed brdf texture is defined, set the texture.
349     if(envTextures.mBrdf)
350     {
351       textureSet.SetTexture(n, envTextures.mBrdf);
352       ++n;
353     }
354
355     if(envTextures.mDiffuse)
356     {
357       textureSet.SetTexture(n, envTextures.mDiffuse);
358       ++n;
359     }
360
361     if(envTextures.mSpecular)
362     {
363       auto specularSampler = Sampler::New();
364       specularSampler.SetWrapMode(WrapMode::CLAMP_TO_EDGE, WrapMode::CLAMP_TO_EDGE, WrapMode::CLAMP_TO_EDGE);
365       specularSampler.SetFilterMode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR);
366
367       textureSet.SetTexture(n, envTextures.mSpecular);
368       textureSet.SetSampler(n, specularSampler);
369       ++n;
370     }
371   }
372   else
373   {
374     ExceptionFlinger(ASSERT_LOCATION) << "Environment index (" << mEnvironmentIdx << ") out of bounds (" << environments.size() << ").";
375   }
376
377   return textureSet;
378 }
379
380 bool MaterialDefinition::CheckTextures(uint32_t flags) const
381 {
382   return std::find_if(mTextureStages.begin(), mTextureStages.end(), [flags](const TextureStage& ts)
383                       { return MaskMatch(ts.mSemantic, flags); }) != mTextureStages.end();
384 }
385
386 } // namespace Loader
387 } // namespace Scene3D
388 } // namespace Dali