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