blob: b7ef0fce1a20d3f4c4d534814fff0548a9a16df4 [file] [log] [blame]
// Copyright 2019 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.
use {
anyhow::Error,
fidl_fuchsia_images as images,
fidl_fuchsia_ui_gfx::{ColorRgb, ColorRgba, DisplayInfo, Vec3},
fuchsia_scenic::{
AmbientLight, Camera, DisplayCompositor, EntityNode, HostImage, HostMemory, Layer,
LayerStack, Material, Rectangle, Renderer, Scene, SessionPtr, ShapeNode,
},
fuchsia_zircon::Time,
png,
std::f32::consts::PI,
std::fs::File,
std::sync::{Arc, Mutex},
};
use crate::graphics_util;
/// A `View` represents the content which is displayed in the Whale Session.
///
/// The view will create all the relevant Scenic resources in a given Scenic session.
#[allow(dead_code)]
pub struct View {
/// The `DisplayCompositor` which the `View` creates in the Scenic session.
compositor: DisplayCompositor,
/// The `LayerStack` which the `View` creates in the Scenic session.
layer_stack: LayerStack,
/// The `Layer` which the `View` creates in the Scenic session.
layer: Layer,
/// The `Renderer` which the `View` creates in the Scenic session.
renderer: Renderer,
/// The `Scene` which the `View` creates in the Scenic session.
scene: Scene,
/// The `Camera` which the `View` creates in the Scenic session.
camera: Camera,
/// The `ShapeNode` which draws the background content of the view.
bg_shape: ShapeNode,
/// The `ShapeNode` which draws the whale image in the view.
rect_shape: ShapeNode,
}
/// A `Context` contains all the information required to update the Scenic session and drive
/// the associated animations etc.
pub struct Context {
/// The time at which the whale animation started, used to calculate the whale rotation.
pub start_time: Time,
/// The current presentation time, used to calculate the whale rotation.
pub presentation_time: Time,
/// The Scenic session associated with the `View`.
pub session: SessionPtr,
/// Information about the display associated with the Scenic session.
pub display_info: DisplayInfo,
}
/// A `ContextPtr` is used to share `Context` in the `App`.
pub type ContextPtr = Arc<Mutex<Context>>;
impl Context {
/// Creates a new `Context` for the given Scenic session, wrapped in a `ContextPtr`.
///
/// # Parameters
/// - `session`: The `SessionPtr` to the Scenic session.
/// - `display_info`: The Scenic session's display info.
///
/// # Returns
/// A `Context` instance wrapped in an `Arc` and `Mutex`.
pub fn new_ptr(session: SessionPtr, display_info: DisplayInfo) -> ContextPtr {
Arc::new(Mutex::new(Context {
presentation_time: Time::get_monotonic(),
start_time: Time::get_monotonic(),
session,
display_info,
}))
}
}
impl View {
/// Creates a new `View` with the associated `context`.
///
/// # Parameters
/// - `context`: The `Context` used to set up the Scenic resources.
///
/// # Returns
/// A `View` if setup was successful, including loading images etc., otherwise an `Error`.
pub fn new(context: ContextPtr) -> Result<View, Error> {
let ctx = context.lock().unwrap();
let session = &ctx.session;
let (width, height) =
(ctx.display_info.width_in_px as f32, ctx.display_info.height_in_px as f32);
let compositor = DisplayCompositor::new(session.clone());
let layer_stack = LayerStack::new(session.clone());
let layer = Layer::new(session.clone());
let renderer = Renderer::new(session.clone());
let scene = Scene::new(session.clone());
let camera = Camera::new(session.clone(), &scene);
compositor.set_layer_stack(&layer_stack);
layer_stack.add_layer(&layer);
layer.set_size(width, height);
layer.set_renderer(&renderer);
renderer.set_camera(&camera);
let ambient_light = AmbientLight::new(session.clone());
ambient_light.set_color(ColorRgb { red: 1.0, green: 1.0, blue: 1.0 });
scene.add_ambient_light(&ambient_light);
let root_node = EntityNode::new(session.clone());
scene.add_child(&root_node);
// Setup the background
let bg_shape = View::make_background(session.clone(), width, height);
root_node.add_child(&bg_shape);
// Create the whale shape
let rect_shape = View::make_whale_shape(session.clone())?;
root_node.add_child(&rect_shape);
Ok(View { compositor, layer_stack, layer, renderer, scene, camera, bg_shape, rect_shape })
}
/// Creates `ShapeNode` representing the background of the `View`.
///
/// # Parameters:
/// - `session`: The Scenic session to create resources in.
/// - `width`: The intended width of the background, in px.
/// - `height`: The intended height of the background, in px.
///
/// # Returns
/// A `ShapeNode` representing the background.
fn make_background(session: SessionPtr, width: f32, height: f32) -> ShapeNode {
let bg_material = Material::new(session.clone());
bg_material.set_color(ColorRgba { red: 255, green: 255, blue: 255, alpha: 255 });
let bg_rect = Rectangle::new(session.clone(), width, height);
let bg_shape = ShapeNode::new(session.clone());
bg_shape.set_shape(&bg_rect);
bg_shape.set_material(&bg_material);
bg_shape.set_translation(width / 2.0, height / 2.0, -50.0);
bg_shape
}
/// Creates a `ShapeNode` containing an image of a whale.
///
/// # Parameters
/// - `session`: The Scenic session to create resources in.
///
/// # Returns
/// A `ShapeNode` containing an image of a whale, or an `Error` if the image loading
/// fails.
fn make_whale_shape(session: SessionPtr) -> Result<ShapeNode, Error> {
let whale_material = View::make_whale_material(session.clone())?;
let rect = Rectangle::new(session.clone(), 500.0, 436.0);
let rect_shape = ShapeNode::new(session.clone());
rect_shape.set_shape(&rect);
rect_shape.set_material(&whale_material);
rect_shape.set_translation(0.0, 0.0, -100.0);
Ok(rect_shape)
}
/// Creates a `Material` containing an appropriately colored image of a whale.
///
/// # Parameters
/// - `session`: The Scenic session to create resources in.
///
/// # Returns
/// A `Material` containing an image of a whale, or an `Error` if the image loading
/// fails.
fn make_whale_material(session: SessionPtr) -> Result<Material, Error> {
let decoder = png::Decoder::new(File::open("/pkg/data/whale.png")?);
let (info, mut reader) = decoder.read_info()?;
let mut buf = vec![0; info.buffer_size()];
reader.next_frame(&mut buf)?;
let px_size_bytes = std::mem::size_of::<u8>() * 4; // RGBA
let (width, height) = (info.width, info.height);
let size_bytes = width as usize * height as usize * px_size_bytes;
let image_info = images::ImageInfo {
transform: images::Transform::Normal,
width,
height,
stride: width * px_size_bytes as u32,
pixel_format: images::PixelFormat::Bgra8,
color_space: images::ColorSpace::Srgb,
tiling: images::Tiling::Linear,
alpha_format: images::AlphaFormat::Premultiplied,
};
let host_memory = HostMemory::allocate(session.clone(), size_bytes)?;
let host_image = HostImage::new(&host_memory, 0, image_info);
// Swizzle RGBA to BGRA.
// Since PNG files always use non-premultiplied alpha in colors
// (https://www.w3.org/TR/PNG-Rationale.html#R.Non-premultiplied-alpha),
// We also convert them to premultiplied alpha bitmaps.
for i in (0..size_bytes).step_by(px_size_bytes) {
let (r, g, b, a) = (buf[i], buf[i + 1], buf[i + 2], buf[i + 3]);
let alpha_factor = (a as f32) / 255.;
let (premultiplied_r, premultiplied_g, premultiplied_b) = (
(r as f32 * alpha_factor) as u8,
(g as f32 * alpha_factor) as u8,
(b as f32 * alpha_factor) as u8,
);
buf[i] = premultiplied_b;
buf[i + 1] = premultiplied_g;
buf[i + 2] = premultiplied_r;
buf[i + 3] = a;
}
host_image.mapping().write(&buf);
let material = Material::new(session);
material.set_color(ColorRgba { red: 70, green: 150, blue: 207, alpha: 128 });
material.set_texture(Some(&host_image));
Ok(material)
}
/// Updates the `View`'s animations to reflect the time in the provided `Context`.
///
/// # Parameters
/// - `context`: The context used to animate correctly.
pub fn update(&self, context: ContextPtr) {
const SPEED: f32 = 0.25;
const SECONDS_PER_NANOSECOND: f32 = 1e-9;
let ctx = context.lock().unwrap();
let t = ((ctx.presentation_time.into_nanos() - ctx.start_time.into_nanos()) as f32
* SECONDS_PER_NANOSECOND
* SPEED)
% 1.0;
let (center_x, center_y) =
(ctx.display_info.width_in_px as f32 / 2.0, ctx.display_info.height_in_px as f32 / 2.0);
const BOUNCE_HEIGHT: f32 = 300.0;
let y_bounce = ((2.0 * t - 1.0).powf(2.0) + 1.0) * BOUNCE_HEIGHT;
self.rect_shape.set_translation(center_x, center_y - BOUNCE_HEIGHT + y_bounce, -100.0);
let angle = t * PI * 2.0;
let quat =
graphics_util::quaternion_from_axis_angle(Vec3 { x: 0.0, y: 0.0, z: 1.0 }, angle);
self.rect_shape.set_rotation(quat.x, quat.y, quat.z, quat.w);
}
}