blob: 9fcd56bd7089b0dbfda79b4234514be5c733c584 [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 "garnet/bin/ui/sketchy/stroke/stroke_tessellator.h"
#include "lib/escher/impl/command_buffer.h"
#include "lib/escher/profiling/timestamp_profiler.h"
#include "lib/escher/vk/buffer.h"
#include "lib/escher/vk/texture.h"
namespace {
constexpr uint32_t kLocalSize = 32;
constexpr char kShaderCode[] = R"GLSL(
#version 450
#extension GL_ARB_separate_shader_objects : enable
struct Bezier2f {
vec2 pts[4];
};
struct Bezier1f {
float pts[4];
};
struct Vertex {
vec2 pos;
vec2 uv;
};
layout(local_size_x = 32) in;
layout(binding = 0) uniform StrokeInfo {
uint segment_count;
float half_width;
uint base_vertex_index;
float pixels_per_division;
uint division_count;
float total_length;
};
layout(std430, binding = 1) buffer ControlPoints {
Bezier2f control_points[];
};
layout(std430, binding = 2) buffer ReParams {
Bezier1f re_params[];
};
layout(std430, binding = 3) buffer DivisionCounts {
uint division_counts[];
};
layout(std430, binding = 4) buffer CumulativeDivisionCounts {
uint cumulative_division_counts[];
};
layout(std430, binding = 5) buffer DivisionSegmentIndices {
uint division_segment_indices[];
};
layout(std430, binding = 6) buffer Vertices {
Vertex vertices[];
};
layout(std430, binding = 7) buffer Indices {
uint indices[];
};
void EvaluatePointAndNormal(in Bezier2f bezier2f, in float t,
out vec2 point, out vec2 normal) {
vec2 tmp3[3];
vec2 tmp2[2];
float t_rest = 1 - t;
tmp3[0] = bezier2f.pts[0] * t_rest + bezier2f.pts[1] * t;
tmp3[1] = bezier2f.pts[1] * t_rest + bezier2f.pts[2] * t;
tmp3[2] = bezier2f.pts[2] * t_rest + bezier2f.pts[3] * t;
tmp2[0] = tmp3[0] * t_rest + tmp3[1] * t;
tmp2[1] = tmp3[1] * t_rest + tmp3[2] * t;
point = tmp2[0] * t_rest + tmp2[1] * t;
vec2 tangent = normalize(tmp2[1] - tmp2[0]);
normal = vec2(-tangent.y, tangent.x);
}
float ReParam(Bezier1f bezier1f, float t) {
float tmp3[3];
float tmp2[2];
float t_rest = 1 - t;
tmp3[0] = bezier1f.pts[0] * t_rest + bezier1f.pts[1] * t;
tmp3[1] = bezier1f.pts[1] * t_rest + bezier1f.pts[2] * t;
tmp3[2] = bezier1f.pts[2] * t_rest + bezier1f.pts[3] * t;
tmp2[0] = tmp3[0] * t_rest + tmp3[1] * t;
tmp2[1] = tmp3[1] * t_rest + tmp3[2] * t;
return tmp2[0] * t_rest + tmp2[1] * t;
}
void main() {
uint division_idx = gl_GlobalInvocationID.x;
if (division_idx >= division_count) {
return;
}
uint segment_idx = division_segment_indices[division_idx];
float division_idx_in_segment =
division_idx - cumulative_division_counts[segment_idx];
float t_before_re_param =
float(division_idx - cumulative_division_counts[segment_idx]) /
division_counts[segment_idx];
float t = ReParam(re_params[segment_idx], t_before_re_param);
float progress = float(division_idx) / division_count;
vec2 point, normal;
EvaluatePointAndNormal(control_points[segment_idx], t, point, normal);
uint vertex_idx = division_idx * 2;
vertices[vertex_idx].pos = point + normal * half_width;
vertices[vertex_idx].uv = vec2(progress, 0);
vertices[vertex_idx + 1].pos = point - normal * half_width;
vertices[vertex_idx + 1].uv = vec2(progress, 0);
if (division_idx < division_count - 1) {
uint index_idx = division_idx * 6;
uint vertex_idx = base_vertex_index + division_idx * 2;
indices[index_idx] = vertex_idx;
indices[index_idx + 1] = vertex_idx + 1;
indices[index_idx + 2] = vertex_idx + 3;
indices[index_idx + 3] = vertex_idx;
indices[index_idx + 4] = vertex_idx + 3;
indices[index_idx + 5] = vertex_idx + 2;
} else {
// division_count is guaranteed to be > 0.
uint index_idx = (division_count - 1) * 6;
// There're no corresponding vertices, so drop the last division.
indices[index_idx] = 0;
indices[index_idx + 1] = 0;
indices[index_idx + 2] = 0;
indices[index_idx + 3] = 0;
indices[index_idx + 4] = 0;
indices[index_idx + 5] = 0;
}
}
)GLSL";
void SetUpMemoryBarrier(const escher::BufferPtr& buffer,
const vk::AccessFlagBits& src_access_mask,
const vk::AccessFlagBits& dst_access_mask,
vk::BufferMemoryBarrier* barrier) {
barrier->srcAccessMask = src_access_mask;
barrier->dstAccessMask = dst_access_mask;
barrier->srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier->dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier->buffer = buffer->vk();
barrier->offset = 0;
barrier->size = buffer->size();
}
} // namespace
namespace sketchy_service {
StrokeTessellator::StrokeTessellator(escher::EscherWeakPtr escher)
: kernel_(std::move(escher), std::vector<vk::ImageLayout>{},
std::vector<vk::DescriptorType>{
// Binding 0: |stroke_info_buffer|
vk::DescriptorType::eUniformBuffer,
// Binding 1: |control_points_buffer|
vk::DescriptorType::eStorageBuffer,
// Binding 2: |re_params_buffer|
vk::DescriptorType::eStorageBuffer,
// Binding 3: |division_counts_buffer|
vk::DescriptorType::eStorageBuffer,
// Binding 4: |cumulative_division_counts_buffer|
vk::DescriptorType::eStorageBuffer,
// Binding 5: |division_segment_index_buffer|
vk::DescriptorType::eStorageBuffer,
// Binding 6: output vertex buffer
vk::DescriptorType::eStorageBuffer,
// Binding 7: output index buffer
vk::DescriptorType::eStorageBuffer},
/* push_constants_size= */ 0, kShaderCode) {}
void StrokeTessellator::Dispatch(
const escher::BufferPtr& stroke_info_buffer,
const escher::BufferPtr& control_points_buffer,
const escher::BufferPtr& re_params_buffer,
const escher::BufferPtr& division_counts_buffer,
const escher::BufferPtr& cumulative_division_counts_buffer,
const escher::BufferPtr& division_segment_index_buffer,
escher::BufferPtr vertex_buffer,
const escher::impl::BufferRange& vertex_range,
escher::BufferPtr index_buffer,
const escher::impl::BufferRange& index_range,
escher::impl::CommandBuffer* command, escher::TimestampProfiler* profiler,
uint32_t division_count, bool apply_barrier) {
if (profiler) {
profiler->AddTimestamp(command, vk::PipelineStageFlagBits::eBottomOfPipe,
"Before Tessellation");
}
if (apply_barrier) {
// Apply barriers if the compute shader depends on memory operations.
// stroke_info_buffer is a uniform buffer that is visible to both host and
// device, and the rest of them use device memory. Therefore, the access
// flag for stroke_info_buffer is eHostWrite, and the rest of them are
// eTransferWrite.
constexpr int kInputBufferCnt = 6;
vk::BufferMemoryBarrier barriers[kInputBufferCnt];
SetUpMemoryBarrier(stroke_info_buffer, vk::AccessFlagBits::eHostWrite,
vk::AccessFlagBits::eShaderRead, barriers);
SetUpMemoryBarrier(control_points_buffer,
vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eShaderRead, barriers + 1);
SetUpMemoryBarrier(re_params_buffer, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eShaderRead, barriers + 2);
SetUpMemoryBarrier(division_counts_buffer,
vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eShaderRead, barriers + 3);
SetUpMemoryBarrier(cumulative_division_counts_buffer,
vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eShaderRead, barriers + 4);
SetUpMemoryBarrier(division_segment_index_buffer,
vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eShaderRead, barriers + 5);
command->vk().pipelineBarrier(
vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eComputeShader, vk::DependencyFlags(), 0,
nullptr, kInputBufferCnt, barriers, 0, nullptr);
}
uint32_t group_count = (division_count + kLocalSize - 1) / kLocalSize;
kernel_.DispatchWithRanges(
std::vector<escher::TexturePtr>{},
{stroke_info_buffer, control_points_buffer, re_params_buffer,
division_counts_buffer, cumulative_division_counts_buffer,
division_segment_index_buffer, std::move(vertex_buffer),
std::move(index_buffer)},
{{0, stroke_info_buffer->size()},
{0, control_points_buffer->size()},
{0, re_params_buffer->size()},
{0, division_counts_buffer->size()},
{0, cumulative_division_counts_buffer->size()},
{0, division_segment_index_buffer->size()},
{vertex_range.offset, vertex_range.size},
{index_range.offset, index_range.size}},
command, group_count,
/* group_count_y= */ 1, /* group_count_z= */ 1,
/* push_constants= */ nullptr);
if (profiler) {
profiler->AddTimestamp(command, vk::PipelineStageFlagBits::eBottomOfPipe,
"After Tessellation");
}
}
} // namespace sketchy_service