2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-scene3d/public-api/loader/material-definition.h>
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>
28 using namespace Toolkit;
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,
47 constexpr SamplerFlags::Type WRAP_MODES_FROM_DALI[]{
48 SamplerFlags::WRAP_CLAMP,
49 SamplerFlags::WRAP_CLAMP,
50 SamplerFlags::WRAP_REPEAT,
51 SamplerFlags::WRAP_MIRROR,
54 constexpr FilterMode::Type FILTER_MODES_TO_DALI[]{
57 FilterMode::NEAREST_MIPMAP_NEAREST,
58 FilterMode::LINEAR_MIPMAP_NEAREST,
59 FilterMode::NEAREST_MIPMAP_LINEAR,
60 FilterMode::LINEAR_MIPMAP_LINEAR,
63 constexpr WrapMode::Type WRAP_MODES_TO_DALI[]{
65 WrapMode::CLAMP_TO_EDGE,
66 WrapMode::MIRRORED_REPEAT};
68 const SamplerFlags::Type GetSingleValueSampler()
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;
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,";
78 Dali::PixelData LoadImageResource(const std::string& resourcePath,
79 TextureDefinition& textureDefinition,
80 FittingMode::Type fittingMode,
81 bool orientationCorrection)
83 Dali::PixelData pixelData;
84 if(!textureDefinition.mTextureBuffer.empty())
86 Dali::Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromBuffer(textureDefinition.mTextureBuffer.data(), textureDefinition.mTextureBuffer.size(), textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
89 pixelData = Devel::PixelBuffer::Convert(pixelBuffer);
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())
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)
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();
103 Dali::Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromBuffer(reinterpret_cast<uint8_t*>(buffer.data()), bufferSize, textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
106 pixelData = Devel::PixelBuffer::Convert(pixelBuffer);
112 textureDefinition.mDirectoryPath = resourcePath;
113 pixelData = SyncImageLoader::Load(resourcePath + textureDefinition.mImageUri, textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
119 SamplerFlags::Type SamplerFlags::Encode(FilterMode::Type minFilter, FilterMode::Type magFilter, WrapMode::Type wrapS, WrapMode::Type wrapT)
121 return FILTER_MODES_FROM_DALI[minFilter] | ((FILTER_MODES_FROM_DALI[magFilter] & FILTER_MAG_BITS) << FILTER_MAG_SHIFT) |
122 (WRAP_MODES_FROM_DALI[wrapS] << WRAP_S_SHIFT) | (WRAP_MODES_FROM_DALI[wrapT] << WRAP_T_SHIFT);
125 FilterMode::Type SamplerFlags::GetMinFilter(Type flags)
127 return FILTER_MODES_TO_DALI[flags & FILTER_MIN_MASK];
130 FilterMode::Type SamplerFlags::GetMagFilter(Type flags)
132 return FILTER_MODES_TO_DALI[(flags >> FILTER_MAG_SHIFT) & FILTER_MAG_MASK];
135 WrapMode::Type SamplerFlags::GetWrapS(Type flags)
137 return WRAP_MODES_TO_DALI[(flags >> WRAP_S_SHIFT) & WRAP_S_MASK];
140 WrapMode::Type SamplerFlags::GetWrapT(Type flags)
142 return WRAP_MODES_TO_DALI[(flags >> WRAP_T_SHIFT) & WRAP_T_MASK];
145 Sampler SamplerFlags::MakeSampler(Type flags)
147 auto sampler = Sampler::New();
148 sampler.SetFilterMode(GetMinFilter(flags), GetMagFilter(flags));
149 sampler.SetWrapMode(GetWrapS(flags), GetWrapT(flags));
153 TextureDefinition::TextureDefinition(const std::string& imageUri, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
154 : mImageUri(imageUri),
155 mSamplerFlags(samplerFlags),
156 mMinImageDimensions(minImageDimensions),
157 mSamplingMode(samplingMode)
161 TextureDefinition::TextureDefinition(std::string&& imageUri, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
162 : mImageUri(std::move(imageUri)),
163 mSamplerFlags(samplerFlags),
164 mMinImageDimensions(minImageDimensions),
165 mSamplingMode(samplingMode)
169 TextureDefinition::TextureDefinition(std::vector<uint8_t>&& textureBuffer, SamplerFlags::Type samplerFlags, ImageDimensions minImageDimensions, SamplingMode::Type samplingMode)
171 mSamplerFlags(samplerFlags),
172 mMinImageDimensions(minImageDimensions),
173 mSamplingMode(samplingMode),
174 mTextureBuffer(std::move(textureBuffer))
178 MaterialDefinition::RawData
179 MaterialDefinition::LoadRaw(const std::string& imagesPath)
183 const bool hasTransparency = MaskMatch(mFlags, TRANSPARENCY);
184 // Why we add additional count here?
185 uint32_t numBuffers = static_cast<uint32_t>(mTextureStages.size()) + (hasTransparency ? !CheckTextures(ALBEDO) + !CheckTextures(METALLIC | ROUGHNESS) + !CheckTextures(NORMAL)
186 : !CheckTextures(ALBEDO | METALLIC) + !CheckTextures(NORMAL | ROUGHNESS));
191 raw.mTextures.reserve(numBuffers);
194 auto iTexture = mTextureStages.begin();
195 auto checkStage = [&](uint32_t flags) {
196 return iTexture != mTextureStages.end() && MaskMatch(iTexture->mSemantic, flags);
199 // Check for compulsory textures: Albedo, Metallic, Roughness, Normal
200 if(checkStage(ALBEDO | METALLIC))
202 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
205 if(checkStage(NORMAL | ROUGHNESS))
207 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
210 else // single value normal-roughness
212 const auto bufferSize = 4;
213 uint8_t* buffer = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff, 0xff}; // normal of (0, 0, 1), roughness of 1
214 raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
219 if(checkStage(ALBEDO))
221 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
224 else if(mNeedAlbedoTexture) // single value albedo, albedo-alpha or albedo-metallic
226 uint32_t bufferSize = 4;
227 uint8_t* buffer = nullptr;
228 auto format = Pixel::Format::RGBA8888;
229 if(hasTransparency) // albedo-alpha
231 buffer = new uint8_t[bufferSize];
232 buffer[3] = static_cast<uint8_t>(mColor.a * 255.f);
234 else if(!checkStage(METALLIC | ROUGHNESS)) // albedo-metallic
236 buffer = new uint8_t[bufferSize];
237 buffer[3] = 0xff; // metallic of 1.0
242 buffer = new uint8_t[bufferSize];
243 format = Pixel::Format::RGB888;
245 buffer[0] = static_cast<uint8_t>(mColor.r * 255.f);
246 buffer[1] = static_cast<uint8_t>(mColor.g * 255.f);
247 buffer[2] = static_cast<uint8_t>(mColor.b * 255.f);
248 raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, format, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
251 // If we have transparency, or an image based albedo map, we will have to continue with separate metallicRoughness + normal.
252 const bool createMetallicRoughnessAndNormal = hasTransparency || std::distance(mTextureStages.begin(), iTexture) > 0;
253 if(checkStage(METALLIC | ROUGHNESS))
255 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
258 else if(createMetallicRoughnessAndNormal && mNeedMetallicRoughnessTexture)
260 // NOTE: we want to set both metallic and roughness to 1.0; dli uses the R & A channels,
261 // glTF2 uses B & G, so we might as well just set all components to 1.0.
262 const auto bufferSize = 4;
263 uint8_t* buffer = new uint8_t[bufferSize]{0xff, 0xff, 0xff, 0xff};
264 raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
267 if(checkStage(NORMAL))
269 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
272 else if(mNeedNormalTexture)
274 if(createMetallicRoughnessAndNormal)
276 const auto bufferSize = 3;
277 uint8_t* buffer = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff}; // normal of (0, 0, 1)
278 raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGB888, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
280 else // single-value normal-roughness
282 const auto bufferSize = 4;
283 uint8_t* buffer = new uint8_t[bufferSize]{0x7f, 0x7f, 0xff, 0xff}; // normal of (0, 0, 1), roughness of 1.0
284 raw.mTextures.push_back({PixelData::New(buffer, bufferSize, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY), GetSingleValueSampler()});
290 if(checkStage(SUBSURFACE))
292 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
296 if(checkStage(OCCLUSION))
298 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
302 if(checkStage(EMISSIVE))
304 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
308 if(checkStage(SPECULAR))
310 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
314 if(checkStage(SPECULAR_COLOR))
316 raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
323 TextureSet MaterialDefinition::Load(const EnvironmentDefinition::Vector& environments, RawData&& raw) const
325 auto textureSet = TextureSet::New();
328 for(auto& tData : raw.mTextures)
330 auto& pixels = tData.mPixels;
334 texture = Texture::New(TextureType::TEXTURE_2D, pixels.GetPixelFormat(), pixels.GetWidth(), pixels.GetHeight());
335 texture.Upload(tData.mPixels, 0, 0, 0, 0, pixels.GetWidth(), pixels.GetHeight());
336 if(tData.mSamplerFlags & SamplerFlags::MIPMAP_MASK)
338 texture.GenerateMipmaps();
342 textureSet.SetTexture(n, texture);
343 textureSet.SetSampler(n, SamplerFlags::MakeSampler(tData.mSamplerFlags));
350 PixelData shadowMapPixelData = PixelData::New(new uint8_t[3]{0xff, 0xff, 0xff}, 3, 1, 1, Pixel::RGB888, PixelData::DELETE_ARRAY);
351 Texture shadowMapTexture = Texture::New(TextureType::TEXTURE_2D, shadowMapPixelData.GetPixelFormat(), shadowMapPixelData.GetWidth(), shadowMapPixelData.GetHeight());
352 shadowMapTexture.Upload(shadowMapPixelData, 0, 0, 0, 0, shadowMapPixelData.GetWidth(), shadowMapPixelData.GetHeight());
353 textureSet.SetTexture(n++, shadowMapTexture);
356 // Assign textures to slots -- starting with 2D ones, then cubemaps, if any.
357 if(mEnvironmentIdx < static_cast<Index>(environments.size()))
359 auto& envTextures = environments[mEnvironmentIdx].second;
360 // If pre-computed brdf texture is defined, set the texture.
361 if(envTextures.mBrdf)
363 textureSet.SetTexture(n, envTextures.mBrdf);
367 if(envTextures.mDiffuse)
369 textureSet.SetTexture(n, envTextures.mDiffuse);
373 if(envTextures.mSpecular)
375 auto specularSampler = Sampler::New();
376 specularSampler.SetWrapMode(WrapMode::CLAMP_TO_EDGE, WrapMode::CLAMP_TO_EDGE, WrapMode::CLAMP_TO_EDGE);
377 specularSampler.SetFilterMode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR);
379 textureSet.SetTexture(n, envTextures.mSpecular);
380 textureSet.SetSampler(n, specularSampler);
386 ExceptionFlinger(ASSERT_LOCATION) << "Environment index (" << mEnvironmentIdx << ") out of bounds (" << environments.size() << ").";
392 bool MaterialDefinition::CheckTextures(uint32_t flags) const
394 return std::find_if(mTextureStages.begin(), mTextureStages.end(), [flags](const TextureStage& ts) { return MaskMatch(ts.mSemantic, flags); }) != mTextureStages.end();
397 } // namespace Loader
398 } // namespace Scene3D