blob: 66c756d7d024782a7fcdb57a513a454f137740e3 [file] [log] [blame]
// Copyright 2018 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 "lib/escher/paper/paper_draw_call_factory.h"
#include <glm/gtc/matrix_access.hpp>
#include "lib/escher/escher.h"
#include "lib/escher/paper/paper_material.h"
#include "lib/escher/paper/paper_render_funcs.h"
#include "lib/escher/paper/paper_render_queue.h"
#include "lib/escher/paper/paper_render_queue_flags.h"
#include "lib/escher/paper/paper_scene.h"
#include "lib/escher/paper/paper_shader_structs.h"
#include "lib/escher/paper/paper_shape_cache.h"
#include "lib/escher/paper/paper_transform_stack.h"
#include "lib/escher/renderer/frame.h"
#include "lib/escher/renderer/render_queue_item.h"
#include "lib/escher/shape/mesh.h"
#include "lib/escher/util/hasher.h"
#include "lib/escher/util/trace_macros.h"
namespace escher {
namespace {
// Default 1x1 texture for Materials that have no texture. See header file
// |white_texture_| comment.
TexturePtr CreateWhiteTexture(Escher* escher) {
FXL_DCHECK(escher);
uint8_t channels[4];
channels[0] = channels[1] = channels[2] = channels[3] = 255;
auto image = escher->NewRgbaImage(1, 1, channels);
return escher->NewTexture(std::move(image), vk::Filter::eNearest);
}
} // anonymous namespace
PaperDrawCallFactory::PaperDrawCallFactory(EscherWeakPtr weak_escher,
const PaperRendererConfig& config)
: white_texture_(CreateWhiteTexture(weak_escher.get())) {}
PaperDrawCallFactory::~PaperDrawCallFactory() { FXL_DCHECK(!frame_); }
void PaperDrawCallFactory::DrawCircle(float radius,
const PaperMaterial& material,
PaperDrawableFlags flags) {
FXL_DCHECK(frame_);
// We aim to improve cache hit rate by using a circle of radius 1. This
// requires us to push a new transform.
const bool scale_radius = (radius != 1.f);
const auto& transform = scale_radius ? transform_stack_->PushScale(radius)
: transform_stack_->Top();
const auto& entry = shape_cache_->GetCircleMesh(
1.f, transform.clip_planes.data(), transform.clip_planes.size());
EnqueueDrawCalls(entry, material, flags);
if (scale_radius)
transform_stack_->Pop();
}
void PaperDrawCallFactory::DrawRect(vec2 min, vec2 max,
const PaperMaterial& material,
PaperDrawableFlags flags) {
FXL_DCHECK(frame_);
const auto& transform = transform_stack_->Top();
const auto& entry = shape_cache_->GetRectMesh(
min, max, transform.clip_planes.data(), transform.clip_planes.size());
EnqueueDrawCalls(entry, material, flags);
}
void PaperDrawCallFactory::DrawRoundedRect(const RoundedRectSpec& spec,
const PaperMaterial& material,
PaperDrawableFlags flags) {
FXL_DCHECK(frame_);
const auto& transform = transform_stack_->Top();
const auto& entry = shape_cache_->GetRoundedRectMesh(
spec, transform.clip_planes.data(), transform.clip_planes.size());
EnqueueDrawCalls(entry, material, flags);
}
void PaperDrawCallFactory::EnqueueDrawCalls(
const PaperShapeCacheEntry& cache_entry, const PaperMaterial& material,
PaperDrawableFlags drawable_flags) {
FXL_DCHECK(frame_);
if (!cache_entry)
return;
TRACE_DURATION("gfx", "PaperDrawCallFactory::EnqueueDrawCalls");
auto* mesh = cache_entry.mesh.get();
const auto& texture =
material.texture() ? material.texture() : white_texture_;
const auto& transform = transform_stack_->Top();
const uint32_t num_indices = cache_entry.num_indices;
const uint32_t num_shadow_volume_indices =
cache_entry.num_shadow_volume_indices;
Hash pipeline_hash;
Hash mesh_hash;
// Only the program goes into the pipeline hash. If we also wanted e.g. some
// objects to be stencil-tested and others not, this info would be included.
{
Hasher h;
// TODO(ES-150): add this back in some way, with a more abstract pipeline
// identifier instead of the actual program uid (which can change from pass
// to pass). h.u64(shadow_volume_program_->uid());
pipeline_hash = h.value();
}
// The object-hash is used to look up an existing MeshData for this
// Mesh/Material pair, and is also used as part of the sort-key below.
// We don't need to take opacity into account because separate RenderQueues
// are used for opaque vs. translucent objects.
{
Hasher h;
h.u64(mesh->uid());
h.u64(texture->uid());
mesh_hash = h.value();
}
PaperRenderFuncs::MeshData* mesh_data;
auto it = object_data_.find(mesh_hash);
if (it != object_data_.end()) {
mesh_data = reinterpret_cast<PaperRenderFuncs::MeshData*>(it->second);
} else {
mesh_data = PaperRenderFuncs::NewMeshData(
frame_, mesh, texture, num_indices, num_shadow_volume_indices);
object_data_.insert(it, std::make_pair(mesh_hash, mesh_data));
}
// Allocate and initialize per-instance data.
PaperRenderFuncs::MeshDrawData* draw_data = PaperRenderFuncs::NewMeshDrawData(
frame_, transform.matrix, material.color(), drawable_flags);
// TODO(ES-103): avoid reaching in to impl::CommandBuffer for keep-alive.
frame_->command_buffer()->KeepAlive(texture.get());
// Compute a depth metric for sorting objects.
#if 1
// As long as the camera is above the top of the viewing volume and the scene
// is composed of parallel-planar surfaces, we can simply subtract the
// object's elevation from the camera's elevation. Given these constraints,
// this metric is superior to the alternate one below, which can provide
// incorrect results at glancing angles (i.e. where the center of one object
// is closer to the camera than the other, but is nevertheless partly behind
// the other object from the camera's perspective.
float depth = camera_pos_.z - transform.matrix[3][2];
#else
// Compute the vector from the camera to the object, and project it against
// the camera's direction to obtain the depth.
float depth = glm::dot(vec3(transform[3]) - camera_pos_, camera_dir_);
#endif
auto sort_key =
material.opaque()
? SortKey::NewOpaque(pipeline_hash, mesh_hash, depth).key()
: SortKey::NewTranslucent(pipeline_hash, mesh_hash, depth).key();
auto queue_flags = material.opaque() ? PaperRenderQueueFlagBits::kOpaque
: PaperRenderQueueFlagBits::kTranslucent;
render_queue_->PushDrawCall(
{.render_queue_item =
{.sort_key = sort_key,
.object_data = mesh_data,
.instance_data = draw_data,
.render_queue_funcs = {PaperRenderFuncs::RenderMesh}},
.render_queue_flags = queue_flags});
}
void PaperDrawCallFactory::SetConfig(const PaperRendererConfig& config) {
// NOTE: nothing currently to do here. This will change, e.g. when we
// add other shadow techniques.
}
std::vector<UniformBinding> PaperDrawCallFactory::BeginFrame(
const FramePtr& frame, PaperScene* scene,
PaperTransformStack* transform_stack, PaperRenderQueue* render_queue,
PaperShapeCache* shape_cache, const Camera& camera) {
FXL_DCHECK(!frame_ && frame && transform_stack && render_queue &&
shape_cache);
frame_ = frame;
transform_stack_ = transform_stack;
render_queue_ = render_queue;
shape_cache_ = shape_cache;
// A camera's transform doesn't move the camera; it is applied to the rest of
// the scene to "move it away from the camera". Therefore, the camera's
// position in the scene can be obtained by inverting it and applying it to
// the origin, or equivalently by inverting the transform and taking the
// rightmost (translation) column.
camera_pos_ = vec3(glm::column(glm::inverse(camera.transform()), 3));
// The camera points down the negative-Z axis, so its world-space direction
// can be obtained by applying the camera transform to the direction vector
// [0, 0, -1, 0] (remembering that directions vectors have a w-coord of 0, vs.
// 1 for position vectors). This is equivalent to taking the negated third
// column of the transform.
camera_dir_ = -vec3(glm::column(camera.transform(), 2));
// Populated below, then returned.
std::vector<UniformBinding> scene_uniforms;
// Generate a UniformBinding for the camera and ambient light data.
{
auto writable_binding =
NewPaperShaderUniformBinding<PaperShaderCameraAmbient>(frame);
writable_binding.first->vp_matrix =
camera.projection() * camera.transform();
writable_binding.first->ambient_light_color = scene->ambient_light.color;
scene_uniforms.push_back(writable_binding.second);
}
// Generate a uniform binding containing data for all point lights, if any.
num_lights_ = scene->num_point_lights();
if (num_lights_ > 0) {
auto writable_binding =
NewPaperShaderUniformBinding<PaperShaderPointLight>(frame, num_lights_);
auto* point_lights = writable_binding.first;
for (size_t i = 0; i < num_lights_; ++i) {
const PaperPointLight& light = scene->point_lights[i];
point_lights[i].position = vec4(light.position, 1);
point_lights[i].color = vec4(light.color, 1);
point_lights[i].falloff = light.falloff;
}
scene_uniforms.push_back(writable_binding.second);
}
return scene_uniforms;
}
void PaperDrawCallFactory::EndFrame() {
FXL_DCHECK(frame_);
frame_ = nullptr;
transform_stack_ = nullptr;
render_queue_ = nullptr;
shape_cache_ = nullptr;
object_data_.clear();
camera_pos_ = vec3(0, 0, 0);
camera_dir_ = vec3(0, 0, 0);
num_lights_ = 0;
}
PaperDrawCallFactory::SortKey PaperDrawCallFactory::SortKey::NewOpaque(
Hash pipeline_hash, Hash draw_hash, float depth) {
// Depth must be non-negative, otherwise comparing the bit representations
// won't work.
FXL_DCHECK(depth >= 0) << " is: " << depth;
// Prioritize minimizing pipeline changes over depth-sorting; both are more
// important than minimizing mesh/texture state changes (in practice, almost
// every draw call uses a separate mesh/texture anyway).
// TODO(ES-105): We currently don't have multiple pipelines used in the opaque
// pass, so we sort primarily by depth. However, when we eventually do have
// multiple pipelines, we may want to rewrite the pipeline hashes with a value
// that reflects whether objects drawn using that pipeline tend to be drawn
// in front or back. For example, if we wanted to draw the background with a
// shiny metallic BRDF, and everything else with a matte diffuse shader then
// we should definitely draw the background last to avoid drawing expensive
// pixels that will later be overdrawn; this isn't guaranteed if we sort by
// the pipeline hash.
uint64_t depth_key(glm::floatBitsToUint(depth));
return SortKey((pipeline_hash.val << 48) | depth_key << 16 |
(draw_hash.val & 0xffff));
}
PaperDrawCallFactory::SortKey PaperDrawCallFactory::SortKey::NewTranslucent(
Hash pipeline_hash, Hash draw_hash, float depth) {
// Depth must be non-negative, otherwise comparing the bit representations
// won't work.
FXL_DCHECK(depth >= 0) << " is: " << depth;
// Prioritize back-to-front order over state changes.
uint64_t depth_key(glm::floatBitsToUint(depth) ^ 0xffffffffu);
return SortKey((depth_key << 32) | (pipeline_hash.val & 0xffff0000u) |
(draw_hash.val & 0xffffu));
}
} // namespace escher