diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index d64965857610b..3a501cc532151 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -1708,25 +1708,13 @@ TEST_P(AiksTest, SaveLayerFiltersScaleWithTransform) { TEST_P(AiksTest, SceneColorSource) { // Load up the scene. auto mapping = - flutter::testing::OpenFixtureAsMapping("flutter_logo.glb.ipscene"); + flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene"); ASSERT_NE(mapping, nullptr); std::shared_ptr gltf_scene = scene::Node::MakeFromFlatbuffer( *mapping, *GetContext()->GetResourceAllocator()); ASSERT_NE(gltf_scene, nullptr); - // Assign a material (temporary stopgap). - std::shared_ptr material = scene::Material::MakeUnlit(); - auto color_baked = CreateTextureForFixture("flutter_logo_baked.png"); - ASSERT_NE(color_baked, nullptr); - material->SetColorTexture(std::move(color_baked)); - material->SetVertexColorWeight(0); - - ASSERT_EQ(gltf_scene->GetChildren().size(), 1u); - ASSERT_EQ(gltf_scene->GetChildren()[0]->GetMesh().GetPrimitives().size(), 1u); - gltf_scene->GetChildren()[0]->GetMesh().GetPrimitives()[0].material = - std::move(material); - auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { Paint paint; diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index 7c8fe01a79e2f..f91c66ef7f11b 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -35,7 +35,7 @@ impeller_shaders("shader_fixtures") { scenec("scene_fixtures") { geometry = [ - "flutter_logo.glb", + "flutter_logo_baked.glb", "two_triangles.glb", ] type = "gltf" @@ -61,8 +61,7 @@ test_fixtures("file_fixtures") { "blue_noise.png", "boston.jpg", "embarcadero.jpg", - "flutter_logo.glb", - "flutter_logo_baked.png", + "flutter_logo_baked.glb", "kalimba.jpg", "multiple_stages.hlsl", "resources_limit.vert", diff --git a/impeller/fixtures/flutter_logo.glb b/impeller/fixtures/flutter_logo.glb deleted file mode 100644 index 8a320e662e963..0000000000000 Binary files a/impeller/fixtures/flutter_logo.glb and /dev/null differ diff --git a/impeller/fixtures/flutter_logo_baked.png b/impeller/fixtures/flutter_logo_baked.glb similarity index 93% rename from impeller/fixtures/flutter_logo_baked.png rename to impeller/fixtures/flutter_logo_baked.glb index fe8477286e8fc..f786be69b36e8 100644 Binary files a/impeller/fixtures/flutter_logo_baked.png and b/impeller/fixtures/flutter_logo_baked.glb differ diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc index f33d4697a1c3a..08800d55bb1b0 100644 --- a/impeller/playground/playground.cc +++ b/impeller/playground/playground.cc @@ -329,16 +329,20 @@ bool Playground::OpenPlaygroundHere(SinglePassCallback pass_callback) { }); } -std::optional Playground::LoadFixtureImageRGBA( - const char* fixture_name) const { - if (!renderer_ || fixture_name == nullptr) { - return std::nullopt; - } - - auto compressed_image = - CompressedImage::Create(OpenAssetAsMapping(fixture_name)); +std::shared_ptr Playground::LoadFixtureImageCompressed( + std::shared_ptr mapping) const { + auto compressed_image = CompressedImage::Create(std::move(mapping)); if (!compressed_image) { VALIDATION_LOG << "Could not create compressed image."; + return nullptr; + } + + return compressed_image; +} + +std::optional Playground::DecodeImageRGBA( + const std::shared_ptr& compressed) const { + if (compressed == nullptr) { return std::nullopt; } // The decoded image is immediately converted into RGBA as that format is @@ -346,9 +350,9 @@ std::optional Playground::LoadFixtureImageRGBA( // bit pixel strides, this is overkill. Since this is a test fixture we // aren't necessarily trying to eke out memory savings here and instead // favor simplicity. - auto image = compressed_image->Decode().ConvertToRGBA(); + auto image = compressed->Decode().ConvertToRGBA(); if (!image.IsValid()) { - VALIDATION_LOG << "Could not find fixture named " << fixture_name; + VALIDATION_LOG << "Could not decode image."; return std::nullopt; } @@ -356,42 +360,52 @@ std::optional Playground::LoadFixtureImageRGBA( } std::shared_ptr Playground::CreateTextureForFixture( - const char* fixture_name, + DecompressedImage& decompressed_image, bool enable_mipmapping) const { - auto image = LoadFixtureImageRGBA(fixture_name); - if (!image.has_value()) { - return nullptr; - } - auto texture_descriptor = TextureDescriptor{}; texture_descriptor.storage_mode = StorageMode::kHostVisible; texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; - texture_descriptor.size = image->GetSize(); + texture_descriptor.size = decompressed_image.GetSize(); texture_descriptor.mip_count = - enable_mipmapping ? image->GetSize().MipCount() : 1u; + enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u; auto texture = renderer_->GetContext()->GetResourceAllocator()->CreateTexture( texture_descriptor); if (!texture) { - VALIDATION_LOG << "Could not allocate texture for fixture " << fixture_name; + VALIDATION_LOG << "Could not allocate texture for fixture."; return nullptr; } - texture->SetLabel(fixture_name); - auto uploaded = texture->SetContents(image->GetAllocation()); + auto uploaded = texture->SetContents(decompressed_image.GetAllocation()); if (!uploaded) { - VALIDATION_LOG << "Could not upload texture to device memory for fixture " - << fixture_name; + VALIDATION_LOG << "Could not upload texture to device memory for fixture."; return nullptr; } return texture; } +std::shared_ptr Playground::CreateTextureForFixture( + std::shared_ptr mapping, + bool enable_mipmapping) const { + auto image = DecodeImageRGBA(LoadFixtureImageCompressed(std::move(mapping))); + if (!image.has_value()) { + return nullptr; + } + return CreateTextureForFixture(image.value()); +} + +std::shared_ptr Playground::CreateTextureForFixture( + const char* fixture_name, + bool enable_mipmapping) const { + return CreateTextureForFixture(OpenAssetAsMapping(fixture_name)); +} + std::shared_ptr Playground::CreateTextureCubeForFixture( std::array fixture_names) const { std::array images; for (size_t i = 0; i < fixture_names.size(); i++) { - auto image = LoadFixtureImageRGBA(fixture_names[i]); + auto image = DecodeImageRGBA( + LoadFixtureImageCompressed(OpenAssetAsMapping(fixture_names[i]))); if (!image.has_value()) { return nullptr; } diff --git a/impeller/playground/playground.h b/impeller/playground/playground.h index 1bc94570d055c..ac44f19450a6d 100644 --- a/impeller/playground/playground.h +++ b/impeller/playground/playground.h @@ -11,6 +11,8 @@ #include "flutter/fml/time/time_delta.h" #include "impeller/geometry/point.h" +#include "impeller/image/compressed_image.h" +#include "impeller/image/decompressed_image.h" #include "impeller/renderer/renderer.h" #include "impeller/renderer/texture.h" #include "impeller/runtime_stage/runtime_stage.h" @@ -61,8 +63,19 @@ class Playground { bool OpenPlaygroundHere(SinglePassCallback pass_callback); - std::optional LoadFixtureImageRGBA( - const char* fixture_name) const; + std::shared_ptr LoadFixtureImageCompressed( + std::shared_ptr mapping) const; + + std::optional DecodeImageRGBA( + const std::shared_ptr& compressed) const; + + std::shared_ptr CreateTextureForFixture( + DecompressedImage& decompressed_image, + bool enable_mipmapping = false) const; + + std::shared_ptr CreateTextureForFixture( + std::shared_ptr mapping, + bool enable_mipmapping = false) const; std::shared_ptr CreateTextureForFixture( const char* fixture_name, diff --git a/impeller/scene/BUILD.gn b/impeller/scene/BUILD.gn index 4dbbc755d57a2..978885867b496 100644 --- a/impeller/scene/BUILD.gn +++ b/impeller/scene/BUILD.gn @@ -27,6 +27,7 @@ impeller_component("scene") { public_deps = [ "../renderer", + "importer:conversions", "importer:importer_flatbuffers", "shaders", ] diff --git a/impeller/scene/importer/BUILD.gn b/impeller/scene/importer/BUILD.gn index 87b9946cb636e..9a017a61ce992 100644 --- a/impeller/scene/importer/BUILD.gn +++ b/impeller/scene/importer/BUILD.gn @@ -17,14 +17,26 @@ flatbuffers("importer_flatbuffers") { public_deps = [ "//third_party/flatbuffers" ] } +impeller_component("conversions") { + sources = [ + "conversions.cc", + "conversions.h", + ] + + public_deps = [ + ":importer_flatbuffers", + "../../base", + "../../geometry", + "//flutter/fml", + ] +} + impeller_component("importer_lib") { # Current versions of libcxx have deprecated some of the UTF-16 string # conversion APIs. defines = [ "_LIBCPP_DISABLE_DEPRECATION_WARNINGS" ] sources = [ - "conversions.cc", - "conversions.h", "importer.h", "importer_gltf.cc", "switches.cc", @@ -35,6 +47,7 @@ impeller_component("importer_lib") { ] public_deps = [ + ":conversions", ":importer_flatbuffers", "../../base", "../../compiler:utilities", diff --git a/impeller/scene/importer/conversions.cc b/impeller/scene/importer/conversions.cc index 7031ed3ba7ccd..bd74f04ebb04e 100644 --- a/impeller/scene/importer/conversions.cc +++ b/impeller/scene/importer/conversions.cc @@ -75,6 +75,14 @@ fb::Color ToFBColor(const Color c) { return fb::Color(c.red, c.green, c.blue, c.alpha); } +std::unique_ptr ToFBColor(const std::vector& c) { + auto* color = new fb::Color(c.size() > 0 ? c[0] : 1, // + c.size() > 1 ? c[1] : 1, // + c.size() > 2 ? c[2] : 1, // + c.size() > 3 ? c[3] : 1); + return std::unique_ptr(color); +} + } // namespace importer } // namespace scene } // namespace impeller diff --git a/impeller/scene/importer/conversions.h b/impeller/scene/importer/conversions.h index 9388e7ed4b79d..b5fe119674d57 100644 --- a/impeller/scene/importer/conversions.h +++ b/impeller/scene/importer/conversions.h @@ -6,6 +6,7 @@ #include #include +#include #include "impeller/geometry/matrix.h" #include "impeller/scene/importer/scene_flatbuffers.h" @@ -44,6 +45,8 @@ fb::Vec4 ToFBVec4(const Vector4 v); fb::Color ToFBColor(const Color c); +std::unique_ptr ToFBColor(const std::vector& c); + } // namespace importer } // namespace scene } // namespace impeller diff --git a/impeller/scene/importer/importer_gltf.cc b/impeller/scene/importer/importer_gltf.cc index 87fe6b73767e8..83c33c0a06e2b 100644 --- a/impeller/scene/importer/importer_gltf.cc +++ b/impeller/scene/importer/importer_gltf.cc @@ -40,6 +40,25 @@ static bool MeshPrimitiveIsSkinned(const tinygltf::Primitive& primitive) { primitive.attributes.find("WEIGHTS_0") != primitive.attributes.end(); } +static void ProcessMaterial(const tinygltf::Model& gltf, + const tinygltf::Material& in_material, + fb::MaterialT& out_material) { + out_material.type = fb::MaterialType::kUnlit; + out_material.base_color_factor = + ToFBColor(in_material.pbrMetallicRoughness.baseColorFactor); + bool base_color_texture_valid = + in_material.pbrMetallicRoughness.baseColorTexture.texCoord == 0 && + in_material.pbrMetallicRoughness.baseColorTexture.index >= 0 && + in_material.pbrMetallicRoughness.baseColorTexture.index < + static_cast(gltf.textures.size()); + out_material.base_color_texture = + base_color_texture_valid + // This is safe because every GLTF input texture is mapped to a + // `Scene->texture`. + ? in_material.pbrMetallicRoughness.baseColorTexture.index + : -1; +} + static bool ProcessMeshPrimitive(const tinygltf::Model& gltf, const tinygltf::Primitive& primitive, fb::MeshPrimitiveT& mesh_primitive) { @@ -119,35 +138,52 @@ static bool ProcessMeshPrimitive(const tinygltf::Model& gltf, /// Indices. /// - if (!WithinRange(primitive.indices, gltf.accessors.size())) { - std::cerr << "Mesh primitive has no index buffer. Skipping." << std::endl; - return false; - } - - auto index_accessor = gltf.accessors[primitive.indices]; - auto index_view = gltf.bufferViews[index_accessor.bufferView]; + { + if (!WithinRange(primitive.indices, gltf.accessors.size())) { + std::cerr << "Mesh primitive has no index buffer. Skipping." << std::endl; + return false; + } - auto indices = std::make_unique(); + auto index_accessor = gltf.accessors[primitive.indices]; + auto index_view = gltf.bufferViews[index_accessor.bufferView]; + + auto indices = std::make_unique(); + + switch (index_accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + indices->type = fb::IndexType::k16Bit; + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + indices->type = fb::IndexType::k32Bit; + break; + default: + std::cerr << "Mesh primitive has unsupported index type " + << index_accessor.componentType << ". Skipping."; + return false; + } + indices->count = index_accessor.count; + indices->data.resize(index_view.byteLength); + const auto* index_buffer = + &gltf.buffers[index_view.buffer].data[index_view.byteOffset]; + std::memcpy(indices->data.data(), index_buffer, indices->data.size()); - switch (index_accessor.componentType) { - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: - indices->type = fb::IndexType::k16Bit; - break; - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: - indices->type = fb::IndexType::k32Bit; - break; - default: - std::cerr << "Mesh primitive has unsupported index type " - << index_accessor.componentType << ". Skipping."; - return false; + mesh_primitive.indices = std::move(indices); } - indices->count = index_accessor.count; - indices->data.resize(index_view.byteLength); - const auto* index_buffer = - &gltf.buffers[index_view.buffer].data[index_view.byteOffset]; - std::memcpy(indices->data.data(), index_buffer, indices->data.size()); - mesh_primitive.indices = std::move(indices); + //--------------------------------------------------------------------------- + /// Material. + /// + + { + auto material = std::make_unique(); + if (primitive.material >= 0 && + primitive.material < static_cast(gltf.materials.size())) { + ProcessMaterial(gltf, gltf.materials[primitive.material], *material); + } else { + material->type = fb::MaterialType::kUnlit; + } + mesh_primitive.material = std::move(material); + } return true; } @@ -208,6 +244,45 @@ static void ProcessNode(const tinygltf::Model& gltf, } } +static void ProcessTexture(const tinygltf::Model& gltf, + const tinygltf::Texture& in_texture, + fb::TextureT& out_texture) { + if (in_texture.source < 0 || + in_texture.source >= static_cast(gltf.images.size())) { + return; + } + auto& image = gltf.images[in_texture.source]; + + auto embedded = std::make_unique(); + embedded->bytes = image.image; + size_t bytes_per_component = 0; + switch (image.pixel_type) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + embedded->component_type = fb::ComponentType::k8Bit; + bytes_per_component = 1; + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + embedded->component_type = fb::ComponentType::k16Bit; + bytes_per_component = 2; + break; + default: + std::cerr << "Texture component type " << image.pixel_type + << " not supported." << std::endl; + return; + } + if (image.image.size() != + bytes_per_component * image.component * image.width * image.height) { + std::cerr << "Decompressed texture had unexpected buffer size. Skipping." + << std::endl; + return; + } + embedded->component_count = image.component; + embedded->width = image.width; + embedded->height = image.height; + out_texture.embedded_image = std::move(embedded); + out_texture.uri = image.uri; +} + bool ParseGLTF(const fml::Mapping& source_mapping, fb::SceneT& out_scene) { tinygltf::Model gltf; @@ -232,6 +307,12 @@ bool ParseGLTF(const fml::Mapping& source_mapping, fb::SceneT& out_scene) { const tinygltf::Scene& scene = gltf.scenes[gltf.defaultScene]; out_scene.children = scene.nodes; + for (size_t texture_i = 0; texture_i < gltf.textures.size(); texture_i++) { + auto texture = std::make_unique(); + ProcessTexture(gltf, gltf.textures[texture_i], *texture); + out_scene.textures.push_back(std::move(texture)); + } + for (size_t node_i = 0; node_i < gltf.nodes.size(); node_i++) { auto node = std::make_unique(); ProcessNode(gltf, gltf.nodes[node_i], *node); diff --git a/impeller/scene/importer/importer_unittests.cc b/impeller/scene/importer/importer_unittests.cc index 1dd20b56169cc..77ffca8e43cb4 100644 --- a/impeller/scene/importer/importer_unittests.cc +++ b/impeller/scene/importer/importer_unittests.cc @@ -15,7 +15,8 @@ namespace importer { namespace testing { TEST(ImporterTest, CanParseUnskinnedGLTF) { - auto mapping = flutter::testing::OpenFixtureAsMapping("flutter_logo.glb"); + auto mapping = + flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb"); fb::SceneT scene; ASSERT_TRUE(ParseGLTF(*mapping, scene)); diff --git a/impeller/scene/importer/scene.fbs b/impeller/scene/importer/scene.fbs index 90552307d3290..de2d1b11b5a08 100644 --- a/impeller/scene/importer/scene.fbs +++ b/impeller/scene/importer/scene.fbs @@ -15,14 +15,28 @@ struct Color { a: float; } -/// A compressed texture for Flutter to decode. The `bytes` field takes -/// precedent over the `uri` field. +enum ComponentType:byte { + k8Bit, + k16Bit, +} + +table EmbeddedImage { + bytes: [ubyte]; + component_count: ubyte = 0; + component_type: ComponentType; + width: uint; + height: uint; +} + +/// The `bytes` field takes precedent over the `uri` field. +/// If both the `uri` and `bytes` fields are empty, a fully opaque white +/// placeholder will be used. table Texture { - /// A Flutter asset URI for an image file to import and decode. + /// A Flutter asset URI for a compressed image file to import and decode. uri: string; - /// Compressed image bytes for Flutter to decode. If this field is not empty, - /// it takes precedent over the `uri` field for sourcing the texture. - bytes: [ubyte]; + /// Decompressed image bytes for uploading to the GPU. If this field is not + /// empty, it takes precedent over the `uri` field for sourcing the texture. + embedded_image: EmbeddedImage; } enum MaterialType:byte { @@ -34,7 +48,7 @@ enum MaterialType:byte { /// by the factor of the component. /// Texture fields are indices into the `Scene`->`textures` array. All textures /// are optional -- a texture index value of -1 indicates no texture. -table Material { +table Material { // When the `MaterialType` is `kUnlit`, only the `base_color` fields are used. type: MaterialType; diff --git a/impeller/scene/material.cc b/impeller/scene/material.cc index 9ea9655a74ae2..9e141d29ec9e2 100644 --- a/impeller/scene/material.cc +++ b/impeller/scene/material.cc @@ -3,9 +3,12 @@ // found in the LICENSE file. #include "impeller/scene/material.h" +#include "impeller/base/validation.h" #include "impeller/renderer/formats.h" #include "impeller/renderer/sampler_descriptor.h" #include "impeller/renderer/sampler_library.h" +#include "impeller/scene/importer/conversions.h" +#include "impeller/scene/importer/scene_flatbuffers.h" #include "impeller/scene/pipeline_key.h" #include "impeller/scene/scene_context.h" #include "impeller/scene/shaders/unlit.frag.h" @@ -21,12 +24,27 @@ namespace scene { Material::~Material() = default; +std::unique_ptr Material::MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures) { + switch (material.type()) { + case fb::MaterialType::kUnlit: + return UnlitMaterial::MakeFromFlatbuffer(material, textures); + case fb::MaterialType::kPhysicallyBased: + return PhysicallyBasedMaterial::MakeFromFlatbuffer(material, textures); + } +} + std::unique_ptr Material::MakeUnlit() { return std::make_unique(); } -std::unique_ptr Material::MakeStandard() { - return std::make_unique(); +std::unique_ptr Material::MakePhysicallyBased() { + return std::make_unique(); +} + +void Material::SetVertexColorWeight(Scalar weight) { + vertex_color_weight_ = weight; } void Material::SetBlendConfig(BlendConfig blend_config) { @@ -50,6 +68,29 @@ SceneContextOptions Material::GetContextOptions(const RenderPass& pass) const { /// UnlitMaterial /// +std::unique_ptr UnlitMaterial::MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures) { + if (material.type() != fb::MaterialType::kUnlit) { + VALIDATION_LOG << "Cannot unpack unlit material because the ipscene " + "material type is not unlit."; + return nullptr; + } + + auto result = Material::MakeUnlit(); + + result->SetColor(material.base_color_factor() + ? importer::ToColor(*material.base_color_factor()) + : Color::White()); + if (material.base_color_texture() >= 0 && + material.base_color_texture() < static_cast(textures.size())) { + result->SetColorTexture(textures[material.base_color_texture()]); + result->SetVertexColorWeight(0); + } + + return result; +} + UnlitMaterial::~UnlitMaterial() = default; void UnlitMaterial::SetColor(Color color) { @@ -60,10 +101,6 @@ void UnlitMaterial::SetColorTexture(std::shared_ptr color_texture) { color_texture_ = std::move(color_texture); } -void UnlitMaterial::SetVertexColorWeight(Scalar weight) { - vertex_color_weight_ = weight; -} - // |Material| MaterialType UnlitMaterial::GetMaterialType() const { return MaterialType::kUnlit; @@ -96,51 +133,96 @@ void UnlitMaterial::BindToCommand(const SceneContext& scene_context, /// StandardMaterial /// -StandardMaterial::~StandardMaterial() = default; - -void StandardMaterial::SetAlbedo(Color albedo) { +std::unique_ptr +PhysicallyBasedMaterial::MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures) { + if (material.type() != fb::MaterialType::kPhysicallyBased) { + VALIDATION_LOG << "Cannot unpack unlit material because the ipscene " + "material type is not unlit."; + return nullptr; + } + + auto result = Material::MakePhysicallyBased(); + + result->SetAlbedo(material.base_color_factor() + ? importer::ToColor(*material.base_color_factor()) + : Color::White()); + result->SetRoughness(material.roughness_factor()); + result->SetMetallic(material.metallic_factor()); + + if (material.base_color_texture() >= 0 && + material.base_color_texture() < static_cast(textures.size())) { + result->SetAlbedoTexture(textures[material.base_color_texture()]); + result->SetVertexColorWeight(0); + } + if (material.metallic_roughness_texture() >= 0 && + material.metallic_roughness_texture() < + static_cast(textures.size())) { + result->SetMetallicRoughnessTexture( + textures[material.metallic_roughness_texture()]); + } + if (material.normal_texture() >= 0 && + material.normal_texture() < static_cast(textures.size())) { + result->SetNormalTexture(textures[material.normal_texture()]); + } + if (material.occlusion_texture() >= 0 && + material.occlusion_texture() < static_cast(textures.size())) { + result->SetOcclusionTexture(textures[material.occlusion_texture()]); + } + + return result; +} + +PhysicallyBasedMaterial::~PhysicallyBasedMaterial() = default; + +void PhysicallyBasedMaterial::SetAlbedo(Color albedo) { albedo_ = albedo; } -void StandardMaterial::SetRoughness(Scalar roughness) { +void PhysicallyBasedMaterial::SetRoughness(Scalar roughness) { roughness_ = roughness; } -void StandardMaterial::SetMetallic(Scalar metallic) { +void PhysicallyBasedMaterial::SetMetallic(Scalar metallic) { metallic_ = metallic; } -void StandardMaterial::SetAlbedoTexture( +void PhysicallyBasedMaterial::SetAlbedoTexture( std::shared_ptr albedo_texture) { albedo_texture_ = std::move(albedo_texture); } -void StandardMaterial::SetNormalTexture( +void PhysicallyBasedMaterial::SetMetallicRoughnessTexture( + std::shared_ptr metallic_roughness_texture) { + metallic_roughness_texture_ = std::move(metallic_roughness_texture); +} + +void PhysicallyBasedMaterial::SetNormalTexture( std::shared_ptr normal_texture) { normal_texture_ = std::move(normal_texture); } -void StandardMaterial::SetOcclusionRoughnessMetallicTexture( - std::shared_ptr occlusion_roughness_metallic_texture) { - occlusion_roughness_metallic_texture_ = - std::move(occlusion_roughness_metallic_texture); +void PhysicallyBasedMaterial::SetOcclusionTexture( + std::shared_ptr occlusion_texture) { + occlusion_texture_ = std::move(occlusion_texture); } -void StandardMaterial::SetEnvironmentMap( +void PhysicallyBasedMaterial::SetEnvironmentMap( std::shared_ptr environment_map) { environment_map_ = std::move(environment_map); } // |Material| -MaterialType StandardMaterial::GetMaterialType() const { +MaterialType PhysicallyBasedMaterial::GetMaterialType() const { // TODO(bdero): Replace this once a PBR shader has landed. return MaterialType::kUnlit; } // |Material| -void StandardMaterial::BindToCommand(const SceneContext& scene_context, - HostBuffer& buffer, - Command& command) const {} +void PhysicallyBasedMaterial::BindToCommand(const SceneContext& scene_context, + HostBuffer& buffer, + Command& command) const {} } // namespace scene } // namespace impeller diff --git a/impeller/scene/material.h b/impeller/scene/material.h index d289a6e8bbd4d..a9221a05f480c 100644 --- a/impeller/scene/material.h +++ b/impeller/scene/material.h @@ -10,6 +10,7 @@ #include "impeller/renderer/formats.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/texture.h" +#include "impeller/scene/importer/scene_flatbuffers.h" #include "impeller/scene/pipeline_key.h" namespace impeller { @@ -20,12 +21,10 @@ struct SceneContextOptions; class Geometry; class UnlitMaterial; -class StandardMaterial; +class PhysicallyBasedMaterial; class Material { public: - virtual ~Material(); - struct BlendConfig { BlendOperation color_op = BlendOperation::kAdd; BlendFactor source_color_factor = BlendFactor::kOne; @@ -40,9 +39,16 @@ class Material { CompareFunction compare = CompareFunction::kAlways; }; + static std::unique_ptr MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures); + static std::unique_ptr MakeUnlit(); - static std::unique_ptr MakeStandard(); + static std::unique_ptr MakePhysicallyBased(); + virtual ~Material(); + + void SetVertexColorWeight(Scalar weight); void SetBlendConfig(BlendConfig blend_config); void SetStencilConfig(StencilConfig stencil_config); @@ -57,6 +63,7 @@ class Material { Command& command) const = 0; protected: + Scalar vertex_color_weight_ = 1; BlendConfig blend_config_; StencilConfig stencil_config_; bool is_translucent_ = false; @@ -64,14 +71,16 @@ class Material { class UnlitMaterial final : public Material { public: + static std::unique_ptr MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures); + ~UnlitMaterial(); void SetColor(Color color); void SetColorTexture(std::shared_ptr color_texture); - void SetVertexColorWeight(Scalar weight); - // |Material| MaterialType GetMaterialType() const override; @@ -83,21 +92,25 @@ class UnlitMaterial final : public Material { private: Color color_ = Color::White(); std::shared_ptr color_texture_; - Scalar vertex_color_weight_ = 1; }; -class StandardMaterial final : public Material { +class PhysicallyBasedMaterial final : public Material { public: - ~StandardMaterial(); + static std::unique_ptr MakeFromFlatbuffer( + const fb::Material& material, + const std::vector>& textures); + + ~PhysicallyBasedMaterial(); void SetAlbedo(Color albedo); void SetRoughness(Scalar roughness); void SetMetallic(Scalar metallic); void SetAlbedoTexture(std::shared_ptr albedo_texture); + void SetMetallicRoughnessTexture( + std::shared_ptr metallic_roughness_texture); void SetNormalTexture(std::shared_ptr normal_texture); - void SetOcclusionRoughnessMetallicTexture( - std::shared_ptr occlusion_roughness_metallic_texture); + void SetOcclusionTexture(std::shared_ptr occlusion_texture); void SetEnvironmentMap(std::shared_ptr environment_map); @@ -111,12 +124,13 @@ class StandardMaterial final : public Material { private: Color albedo_ = Color::White(); - Scalar roughness_ = 0.5; Scalar metallic_ = 0.5; + Scalar roughness_ = 0.5; std::shared_ptr albedo_texture_; + std::shared_ptr metallic_roughness_texture_; std::shared_ptr normal_texture_; - std::shared_ptr occlusion_roughness_metallic_texture_; + std::shared_ptr occlusion_texture_; std::shared_ptr environment_map_; }; diff --git a/impeller/scene/node.cc b/impeller/scene/node.cc index c2d1521419741..cabdb197c27f7 100644 --- a/impeller/scene/node.cc +++ b/impeller/scene/node.cc @@ -6,8 +6,10 @@ #include +#include "flutter/fml/logging.h" #include "impeller/base/validation.h" #include "impeller/geometry/matrix.h" +#include "impeller/scene/importer/conversions.h" #include "impeller/scene/importer/scene_flatbuffers.h" #include "impeller/scene/mesh.h" #include "impeller/scene/node.h" @@ -16,20 +18,107 @@ namespace impeller { namespace scene { -std::shared_ptr Node::MakeFromFlatbuffer(fml::Mapping& mapping, - Allocator& allocator) { - flatbuffers::Verifier verifier(mapping.GetMapping(), mapping.GetSize()); +std::shared_ptr Node::MakeFromFlatbuffer( + const fml::Mapping& ipscene_mapping, + Allocator& allocator) { + flatbuffers::Verifier verifier(ipscene_mapping.GetMapping(), + ipscene_mapping.GetSize()); if (!fb::VerifySceneBuffer(verifier)) { VALIDATION_LOG << "Failed to unpack scene: Scene flatbuffer is invalid."; return nullptr; } - return Node::MakeFromFlatbuffer(*fb::GetScene(mapping.GetMapping()), + return Node::MakeFromFlatbuffer(*fb::GetScene(ipscene_mapping.GetMapping()), allocator); } +static std::shared_ptr UnpackTextureFromFlatbuffer( + const fb::Texture* iptexture, + Allocator& allocator) { + if (iptexture == nullptr || iptexture->embedded_image() == nullptr || + iptexture->embedded_image()->bytes() == nullptr) { + return nullptr; + } + + auto embedded = iptexture->embedded_image(); + + uint8_t bytes_per_component = 0; + switch (embedded->component_type()) { + case fb::ComponentType::k8Bit: + bytes_per_component = 1; + break; + case fb::ComponentType::k16Bit: + // bytes_per_component = 2; + FML_LOG(WARNING) << "16 bit textures not yet supported."; + return nullptr; + } + + DecompressedImage::Format format; + switch (embedded->component_count()) { + case 1: + format = DecompressedImage::Format::kGrey; + break; + case 3: + format = DecompressedImage::Format::kRGB; + break; + case 4: + format = DecompressedImage::Format::kRGBA; + break; + default: + FML_LOG(WARNING) << "Textures with " << embedded->component_count() + << " components are not supported." << std::endl; + return nullptr; + } + if (embedded->bytes()->size() != bytes_per_component * + embedded->component_count() * + embedded->width() * embedded->height()) { + FML_LOG(WARNING) << "Embedded texture has an unexpected size. Skipping." + << std::endl; + return nullptr; + } + + auto image_mapping = std::make_shared( + embedded->bytes()->Data(), embedded->bytes()->size()); + auto decompressed_image = + DecompressedImage(ISize(embedded->width(), embedded->height()), format, + image_mapping) + .ConvertToRGBA(); + + auto texture_descriptor = TextureDescriptor{}; + texture_descriptor.storage_mode = StorageMode::kHostVisible; + texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; + texture_descriptor.size = decompressed_image.GetSize(); + // TODO(bdero): Generate mipmaps for embedded textures. + texture_descriptor.mip_count = 1u; + + auto texture = allocator.CreateTexture(texture_descriptor); + if (!texture) { + FML_LOG(ERROR) << "Could not allocate texture."; + return nullptr; + } + + auto uploaded = texture->SetContents(decompressed_image.GetAllocation()); + if (!uploaded) { + FML_LOG(ERROR) << "Could not upload texture to device memory."; + return nullptr; + } + + return texture; +} + std::shared_ptr Node::MakeFromFlatbuffer(const fb::Scene& scene, Allocator& allocator) { + // Unpack textures. + std::vector> textures; + if (scene.textures()) { + for (const auto iptexture : *scene.textures()) { + // The elements of the unpacked texture array must correspond exactly with + // the ipscene texture array. So if a texture is empty or invalid, a + // nullptr is inserted as a placeholder. + textures.push_back(UnpackTextureFromFlatbuffer(iptexture, allocator)); + } + } + auto result = std::make_shared(); if (!scene.nodes() || !scene.children()) { return result; // The scene is empty. @@ -55,7 +144,7 @@ std::shared_ptr Node::MakeFromFlatbuffer(const fb::Scene& scene, // Unpack each node. for (size_t node_i = 0; node_i < scene.nodes()->size(); node_i++) { scene_nodes[node_i]->UnpackFromFlatbuffer(*scene.nodes()->Get(node_i), - scene_nodes, allocator); + scene_nodes, textures, allocator); } return result; @@ -64,16 +153,29 @@ std::shared_ptr Node::MakeFromFlatbuffer(const fb::Scene& scene, void Node::UnpackFromFlatbuffer( const fb::Node& source_node, const std::vector>& scene_nodes, + const std::vector>& textures, Allocator& allocator) { + /// Transform. + + SetLocalTransform(importer::ToMatrix(*source_node.transform())); + + /// Meshes. + if (source_node.mesh_primitives()) { Mesh mesh; for (const auto* primitives : *source_node.mesh_primitives()) { auto geometry = Geometry::MakeFromFlatbuffer(*primitives, allocator); - mesh.AddPrimitive({geometry, Material::MakeUnlit()}); + auto material = + primitives->material() + ? Material::MakeFromFlatbuffer(*primitives->material(), textures) + : Material::MakeUnlit(); + mesh.AddPrimitive({std::move(geometry), std::move(material)}); } SetMesh(std::move(mesh)); } + /// Child nodes. + if (!source_node.children()) { return; } diff --git a/impeller/scene/node.h b/impeller/scene/node.h index 307411de6d053..a64fa9b8d9d84 100644 --- a/impeller/scene/node.h +++ b/impeller/scene/node.h @@ -11,6 +11,7 @@ #include "impeller/geometry/matrix.h" #include "impeller/renderer/render_target.h" +#include "impeller/renderer/texture.h" #include "impeller/scene/camera.h" #include "impeller/scene/mesh.h" #include "impeller/scene/scene_encoder.h" @@ -20,8 +21,9 @@ namespace scene { class Node final { public: - static std::shared_ptr MakeFromFlatbuffer(fml::Mapping& mapping, - Allocator& allocator); + static std::shared_ptr MakeFromFlatbuffer( + const fml::Mapping& ipscene_mapping, + Allocator& allocator); static std::shared_ptr MakeFromFlatbuffer(const fb::Scene& scene, Allocator& allocator); @@ -52,6 +54,7 @@ class Node final { void UnpackFromFlatbuffer( const fb::Node& node, const std::vector>& scene_nodes, + const std::vector>& textures, Allocator& allocator); bool is_root_ = false; diff --git a/impeller/scene/scene_unittests.cc b/impeller/scene/scene_unittests.cc index a9ce2138c19c6..0cbd458fb9f54 100644 --- a/impeller/scene/scene_unittests.cc +++ b/impeller/scene/scene_unittests.cc @@ -4,13 +4,16 @@ #include #include +#include +#include "flutter/fml/mapping.h" #include "flutter/testing/testing.h" #include "impeller/geometry/color.h" #include "impeller/geometry/constants.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/quaternion.h" #include "impeller/geometry/vector.h" +#include "impeller/image/decompressed_image.h" #include "impeller/playground/playground.h" #include "impeller/playground/playground_test.h" #include "impeller/renderer/formats.h" @@ -72,22 +75,17 @@ TEST_P(SceneTest, FlutterLogo) { auto allocator = GetContext()->GetResourceAllocator(); auto mapping = - flutter::testing::OpenFixtureAsMapping("flutter_logo.glb.ipscene"); + flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene"); ASSERT_NE(mapping, nullptr); + flatbuffers::Verifier verifier(mapping->GetMapping(), mapping->GetSize()); + ASSERT_TRUE(fb::VerifySceneBuffer(verifier)); + std::shared_ptr gltf_scene = Node::MakeFromFlatbuffer(*mapping, *allocator); ASSERT_NE(gltf_scene, nullptr); - - std::shared_ptr material = Material::MakeUnlit(); - auto color_baked = CreateTextureForFixture("flutter_logo_baked.png"); - material->SetColorTexture(color_baked); - material->SetVertexColorWeight(0); - ASSERT_EQ(gltf_scene->GetChildren().size(), 1u); ASSERT_EQ(gltf_scene->GetChildren()[0]->GetMesh().GetPrimitives().size(), 1u); - gltf_scene->GetChildren()[0]->GetMesh().GetPrimitives()[0].material = - material; auto scene_context = std::make_shared(GetContext()); auto scene = Scene(scene_context);