blob: 9b162156dc043a9434cd760d8cb9bc84b6481df0 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/scenic/lib/gfx/snapshot/snapshotter.h"
#include <lib/fit/function.h>
#include <lib/zx/vmo.h>
#include "src/lib/fsl/vmo/sized_vmo.h"
#include "src/lib/fsl/vmo/vector.h"
#include "src/lib/fxl/logging.h"
#include "src/ui/lib/escher/escher.h"
#include "src/ui/lib/escher/third_party/granite/vk/command_buffer.h"
#include "src/ui/lib/escher/util/trace_macros.h"
#include "src/ui/lib/escher/vk/image.h"
#include "src/ui/scenic/lib/gfx/engine/engine.h"
#include "src/ui/scenic/lib/gfx/resources/buffer.h"
#include "src/ui/scenic/lib/gfx/resources/camera.h"
#include "src/ui/scenic/lib/gfx/resources/compositor/display_compositor.h"
#include "src/ui/scenic/lib/gfx/resources/compositor/layer.h"
#include "src/ui/scenic/lib/gfx/resources/compositor/layer_stack.h"
#include "src/ui/scenic/lib/gfx/resources/image.h"
#include "src/ui/scenic/lib/gfx/resources/image_pipe_base.h"
#include "src/ui/scenic/lib/gfx/resources/lights/ambient_light.h"
#include "src/ui/scenic/lib/gfx/resources/lights/directional_light.h"
#include "src/ui/scenic/lib/gfx/resources/lights/point_light.h"
#include "src/ui/scenic/lib/gfx/resources/material.h"
#include "src/ui/scenic/lib/gfx/resources/memory.h"
#include "src/ui/scenic/lib/gfx/resources/nodes/entity_node.h"
#include "src/ui/scenic/lib/gfx/resources/nodes/opacity_node.h"
#include "src/ui/scenic/lib/gfx/resources/nodes/scene.h"
#include "src/ui/scenic/lib/gfx/resources/nodes/shape_node.h"
#include "src/ui/scenic/lib/gfx/resources/renderers/renderer.h"
#include "src/ui/scenic/lib/gfx/resources/resource_visitor.h"
#include "src/ui/scenic/lib/gfx/resources/shapes/circle_shape.h"
#include "src/ui/scenic/lib/gfx/resources/shapes/mesh_shape.h"
#include "src/ui/scenic/lib/gfx/resources/shapes/rectangle_shape.h"
#include "src/ui/scenic/lib/gfx/resources/shapes/rounded_rectangle_shape.h"
#include "src/ui/scenic/lib/gfx/resources/view.h"
#include "src/ui/scenic/lib/gfx/resources/view_holder.h"
#include "src/ui/scenic/lib/gfx/snapshot/version.h"
namespace scenic_impl {
namespace gfx {
namespace {
// Helper function to create a |SizedVmo| from bytes of certain size.
bool VmoFromBytes(const uint8_t* bytes, size_t num_bytes, uint32_t type, uint32_t version,
fsl::SizedVmo* sized_vmo_ptr) {
FXL_CHECK(sized_vmo_ptr);
zx::vmo vmo;
size_t total_size = num_bytes + sizeof(type) + sizeof(version);
zx_status_t status = zx::vmo::create(total_size, 0u, &vmo);
if (status < 0) {
FXL_PLOG(WARNING, status) << "zx::vmo::create failed";
return false;
}
if (num_bytes > 0) {
status = vmo.write(&type, 0, sizeof(uint32_t));
if (status < 0) {
FXL_PLOG(WARNING, status) << "zx::vmo::write snapshot type failed";
return false;
}
status = vmo.write(&version, sizeof(uint32_t), sizeof(uint32_t));
if (status < 0) {
FXL_PLOG(WARNING, status) << "zx::vmo::write snapshot version failed";
return false;
}
status = vmo.write(bytes, 2 * sizeof(uint32_t), num_bytes);
if (status < 0) {
FXL_PLOG(WARNING, status) << "zx::vmo::write bytes failed";
return false;
}
}
*sized_vmo_ptr = fsl::SizedVmo(std::move(vmo), total_size);
return true;
}
} // namespace
escher::ImagePtr Snapshotter::CreateReplacementImage(uint32_t width, uint32_t height) {
// Lazy creation.
if (!gpu_uploader_for_replacements_) {
gpu_uploader_for_replacements_ = escher::BatchGpuUploader::New(escher_);
}
// Fuchsia colors
// TODO(41024): data for a single pixel is provided, but there should be data for width * height
// pixels.
uint8_t channels[4];
channels[1] = 119;
channels[0] = channels[2] = channels[3] = 255;
return escher_->NewRgbaImage(gpu_uploader_for_replacements_.get(), width, height, channels);
}
Snapshotter::Snapshotter(escher::EscherWeakPtr escher)
: gpu_downloader_(
escher::BatchGpuDownloader::New(escher, escher::CommandBuffer::Type::kGraphics)),
escher_(escher) {}
void Snapshotter::TakeSnapshot(Resource* resource, TakeSnapshotCallback snapshot_callback) {
FXL_DCHECK(resource) << "Cannot snapshot null resource.";
// Visit the scene graph starting with |resource| and collecting images
// and buffers to read from the GPU.
resource->Accept(this);
auto content_ready_callback = [rounded_rect_data_vec = std::move(rounded_rect_data_vec_),
node_serializer = current_node_serializer_,
snapshot_callback = std::move(snapshot_callback)]() {
TRACE_DURATION("gfx", "Snapshotter::Serialize");
flatbuffers::FlatBufferBuilder builder;
builder.Finish(node_serializer->serialize(builder));
fsl::SizedVmo sized_vmo;
if (!VmoFromBytes(builder.GetBufferPointer(), builder.GetSize(),
SnapshotData::SnapshotType::kFlatBuffer, SnapshotData::SnapshotVersion::v1_0,
&sized_vmo)) {
return snapshot_callback(fuchsia::mem::Buffer{}, false);
} else {
return snapshot_callback(std::move(sized_vmo).ToTransport(), true);
}
};
// If we needed to upload any replacement images for protected memory, do that first,
// and make the "downloading uploader" wait on this upload.
// TODO(before-41029): would be more efficient to just serialize fake data directly, but
// that would require significant changes to snapshotter.
if (gpu_uploader_for_replacements_) {
FXL_DCHECK(gpu_uploader_for_replacements_->HasContentToUpload());
auto replacement_semaphore = escher::Semaphore::New(escher_->vk_device());
gpu_uploader_for_replacements_->AddSignalSemaphore(replacement_semaphore);
gpu_uploader_for_replacements_->Submit();
gpu_downloader_->AddWaitSemaphore(std::move(replacement_semaphore),
vk::PipelineStageFlagBits::eTransfer);
}
// If the Snapshotter has a Engine binding, we need to ensure that the
// commands in |gpu_downloader_| are executed after commands in the engine's
// command buffer.
if (escher_ && escher_->semaphore_chain() && gpu_downloader_->HasContentToDownload()) {
auto semaphore_pair = escher_->semaphore_chain()->TakeLastAndCreateNextSemaphore();
gpu_downloader_->AddSignalSemaphore(std::move(semaphore_pair.semaphore_to_signal));
gpu_downloader_->AddWaitSemaphore(std::move(semaphore_pair.semaphore_to_wait),
vk::PipelineStageFlagBits::eTransfer);
}
// The |content_ready_callback| will be always called no matter whether we
// have contents to download or not.
gpu_downloader_->Submit(std::move(content_ready_callback));
}
void Snapshotter::Visit(EntityNode* r) { VisitNode(r); }
void Snapshotter::Visit(OpacityNode* r) { VisitNode(r); }
void Snapshotter::Visit(ShapeNode* r) {
if (r->shape() && r->material()) {
VisitNode(r);
r->shape()->Accept(this);
r->material()->Accept(this);
}
}
void Snapshotter::Visit(Scene* r) {
// TODO(SCN-1221): Should handle Scene better, e.g. storing the lights.
VisitNode(r);
}
void Snapshotter::Visit(CircleShape* r) {
FXL_DCHECK(current_node_serializer_);
auto shape = std::make_shared<CircleSerializer>();
shape->radius = r->radius();
current_node_serializer_->shape = shape;
}
void Snapshotter::Visit(RectangleShape* r) {
FXL_DCHECK(current_node_serializer_);
auto shape = std::make_shared<RectangleSerializer>();
shape->width = r->width();
shape->height = r->height();
current_node_serializer_->shape = shape;
}
void Snapshotter::Visit(RoundedRectangleShape* r) {
FXL_DCHECK(current_node_serializer_);
auto shape = std::make_shared<RoundedRectangleSerializer>();
shape->width = r->width();
shape->height = r->height();
shape->top_left_radius = r->top_left_radius();
shape->top_right_radius = r->top_right_radius();
shape->bottom_right_radius = r->bottom_right_radius();
shape->bottom_left_radius = r->bottom_left_radius();
current_node_serializer_->shape = shape;
VisitRoundedRectSpec(r->spec());
}
void Snapshotter::Visit(MeshShape* r) {
FXL_DCHECK(current_node_serializer_);
current_node_serializer_->shape = std::make_shared<MeshSerializer>();
VisitMesh(r->escher_mesh());
}
void Snapshotter::Visit(Material* r) {
if (r->texture_image()) {
r->texture_image()->Accept(this);
} else {
auto color = std::make_shared<ColorSerializer>();
color->red = r->red();
color->green = r->green();
color->blue = r->blue();
color->alpha = r->alpha();
current_node_serializer_->material = color;
}
VisitResource(r);
}
void Snapshotter::Visit(Memory* r) { VisitResource(r); }
void Snapshotter::Visit(Image* r) {
VisitImage(r->GetEscherImage());
VisitResource(r);
}
void Snapshotter::Snapshotter::Visit(ImagePipeBase* r) {
VisitImage(r->GetEscherImage());
VisitResource(r);
}
void Snapshotter::Visit(Buffer* r) { VisitResource(r); }
void Snapshotter::Visit(View* r) { VisitResource(r); }
void Snapshotter::Visit(ViewNode* r) { VisitNode(r); }
void Snapshotter::Visit(ViewHolder* r) { VisitNode(r); }
void Snapshotter::Visit(Compositor* r) { r->layer_stack()->Accept(this); }
void Snapshotter::Visit(DisplayCompositor* r) { r->layer_stack()->Accept(this); }
void Snapshotter::Visit(LayerStack* r) {
for (auto& layer : r->layers()) {
layer->Accept(this);
}
}
void Snapshotter::Visit(Layer* r) { r->renderer()->Accept(this); }
void Snapshotter::Visit(Camera* r) { r->scene()->Accept(this); }
void Snapshotter::Visit(Renderer* r) { r->camera()->Accept(this); }
void Snapshotter::Visit(Light* r) { VisitResource(r); }
void Snapshotter::Visit(AmbientLight* r) { VisitResource(r); }
void Snapshotter::Visit(DirectionalLight* r) { VisitResource(r); }
void Snapshotter::Visit(PointLight* r) { VisitResource(r); }
void Snapshotter::VisitNode(Node* r) {
auto parent_serializer = current_node_serializer_;
auto node_serializer = std::make_shared<NodeSerializer>();
if (parent_serializer) {
parent_serializer->children.push_back(node_serializer);
}
// Name.
node_serializer->name = r->label();
// Transform.
if (!r->transform().IsIdentity()) {
auto transform = std::make_shared<TransformSerializer>();
node_serializer->transform = transform;
auto& translation = r->translation();
transform->translation = snapshot::Vec3(translation.x, translation.y, translation.z);
auto& scale = r->scale();
transform->scale = snapshot::Vec3(scale.x, scale.y, scale.z);
auto& rotation = r->rotation();
transform->rotation = snapshot::Quat(rotation.x, rotation.y, rotation.z, rotation.w);
auto& anchor = r->anchor();
transform->anchor = snapshot::Vec3(anchor.x, anchor.y, anchor.z);
}
// Children.
for (auto& child : r->children()) {
// Set current node to this node during children traversal.
current_node_serializer_ = node_serializer;
child->Accept(this);
}
// Set current node to this node during children traversal.
current_node_serializer_ = node_serializer;
VisitResource(r);
current_node_serializer_ = node_serializer;
}
void Snapshotter::VisitResource(Resource* r) {}
void Snapshotter::VisitImage(escher::ImagePtr image) {
if (!image) {
return;
}
if (image->use_protected_memory()) {
// We are not allowed to readback protected memory.
image = CreateReplacementImage(image->width(), image->height());
}
auto format = (int32_t)image->format();
auto width = image->width();
auto height = image->height();
ReadImage(image, [format, width, height, node_serializer = current_node_serializer_](
const void* host_ptr, size_t size) {
auto image = std::make_shared<ImageSerializer>(format, width, height, host_ptr, size);
node_serializer->material = image;
});
}
void Snapshotter::VisitMesh(escher::MeshPtr mesh) {
FXL_DCHECK(current_node_serializer_);
auto geometry = std::make_shared<GeometrySerializer>();
geometry->bbox_min = snapshot::Vec3(mesh->bounding_box().min().x, mesh->bounding_box().min().y,
mesh->bounding_box().min().z);
geometry->bbox_max = snapshot::Vec3(mesh->bounding_box().max().x, mesh->bounding_box().max().y,
mesh->bounding_box().max().z);
current_node_serializer_->mesh = geometry;
for (int i = -1; i < (int)mesh->attribute_buffers().size(); i++) {
// -1 implies index buffer, >=0 is attribute buffer.
bool is_index_buffer = i == -1;
auto src_buffer = is_index_buffer ? mesh->index_buffer() : mesh->attribute_buffer(i).buffer;
// Attribute buffer other than primarily attribute buffers can be null.
if (!src_buffer) {
continue;
}
ReadBuffer(src_buffer, [geometry, is_index_buffer, mesh](const void* host_ptr, size_t size) {
if (is_index_buffer) {
auto indices = std::make_shared<IndexBufferSerializer>(mesh->num_indices(), host_ptr, size);
geometry->indices = indices;
} else {
auto attribute = std::make_shared<AttributeBufferSerializer>(
/* vertex_count */ mesh->num_vertices(), /* stride */ mesh->spec().stride(0), host_ptr,
size);
geometry->attributes.push_back(attribute);
}
});
}
}
// This function tessellates a new rounded rect mesh and writes out the mesh
// data to the geometry serializer. This avoids having to read in an existing
// GPU mesh buffer.
//
// To ensure that the tessellated mesh data remains alive long enough for it
// to be serialized after this traversal is over, the data is stored in a
// |RoundedRectData| struct which is stored in an array, to be cleared after
// serialization is complete.
void Snapshotter::VisitRoundedRectSpec(const escher::RoundedRectSpec& spec) {
FXL_DCHECK(current_node_serializer_);
auto geometry = std::make_shared<GeometrySerializer>();
// Create the mesh spec and make sure that the attribute offsets match those
// of the PosUvVertex struct. Also make sure that the total stride is equal to
// to the size of PosUvVertex. Index type sizes must also match.
const escher::MeshSpec mesh_spec{
{escher::MeshAttribute::kPosition2D | escher::MeshAttribute::kUV}};
FXL_DCHECK(mesh_spec.attribute_offset(0, escher::MeshAttribute::kPosition2D) ==
offsetof(PosUvVertex, pos))
<< "Position offsets do not match.";
FXL_DCHECK(mesh_spec.attribute_offset(0, escher::MeshAttribute::kUV) == offsetof(PosUvVertex, uv))
<< "UV offsets do not match.";
FXL_DCHECK(mesh_spec.stride(0) == sizeof(PosUvVertex)) << "Vertex strides do not match.";
FXL_DCHECK(sizeof(escher::MeshSpec::IndexType) == sizeof(uint32_t))
<< "Index type sizes do not match.";
// Grab the counts for indices.
uint32_t vertex_count;
uint32_t index_count;
std::tie(vertex_count, index_count) = GetRoundedRectMeshVertexAndIndexCounts(spec);
// Create a new RoundedRectData struct.
RoundedRectData rect_data;
rect_data.indices.resize(index_count);
rect_data.vertices.resize(vertex_count);
// Calculate the total number of bytes needed for the indices and vertices.
const uint32_t kIndexBytes = sizeof(escher::MeshSpec::IndexType) * rect_data.indices.size();
const uint32_t kVertexBytes = sizeof(PosUvVertex) * rect_data.vertices.size();
// Now actually generate the data for the indices and vertices.
GenerateRoundedRectIndices(spec, mesh_spec, rect_data.indices.data(), kIndexBytes);
GenerateRoundedRectVertices(spec, mesh_spec, rect_data.vertices.data(), kVertexBytes);
// Get the bounding box from the RoundedRectSpec.
const escher::BoundingBox bounding_box(-0.5f * escher::vec3(spec.width, spec.height, 0),
0.5f * escher::vec3(spec.width, spec.height, 0));
// Write out the bounding box data to the geometry serializer.
geometry->bbox_min =
snapshot::Vec3(bounding_box.min().x, bounding_box.min().y, bounding_box.min().z);
geometry->bbox_max =
snapshot::Vec3(bounding_box.max().x, bounding_box.max().y, bounding_box.max().z);
current_node_serializer_->mesh = geometry;
// Write out the index data to the geometry serializer.
geometry->indices =
std::make_shared<IndexBufferSerializer>(index_count, rect_data.indices.data(), kIndexBytes);
// Write out the vertex data to the geometry serializer.
geometry->attributes.push_back(std::make_shared<AttributeBufferSerializer>(
/* vertex_count */ vertex_count, /* stride */ mesh_spec.stride(0), rect_data.vertices.data(),
kVertexBytes));
// Add the rect data to rounded rect vector so it can be kept alive until after serialization
// is complete. Then the vector will be cleared.
rounded_rect_data_vec_.push_back(std::move(rect_data));
}
void Snapshotter::ReadImage(const escher::ImagePtr& image,
escher::BatchGpuDownloader::Callback callback) {
gpu_downloader_->ScheduleReadImage(image, std::move(callback));
}
void Snapshotter::ReadBuffer(const escher::BufferPtr& buffer,
escher::BatchGpuDownloader::Callback callback) {
gpu_downloader_->ScheduleReadBuffer(buffer, std::move(callback));
}
} // namespace gfx
} // namespace scenic_impl