531 lines
16 KiB
C++
531 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2016 Google, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <array>
|
|
#include <unordered_map>
|
|
|
|
#include "Helpers.h"
|
|
#include "Meshes.h"
|
|
|
|
namespace {
|
|
|
|
class Mesh {
|
|
public:
|
|
struct Position {
|
|
float x;
|
|
float y;
|
|
float z;
|
|
};
|
|
|
|
struct Normal {
|
|
float x;
|
|
float y;
|
|
float z;
|
|
};
|
|
|
|
struct Face {
|
|
int v0;
|
|
int v1;
|
|
int v2;
|
|
};
|
|
|
|
static uint32_t vertex_stride()
|
|
{
|
|
// Position + Normal
|
|
const int comp_count = 6;
|
|
|
|
return sizeof(float) * comp_count;
|
|
}
|
|
|
|
static VkVertexInputBindingDescription vertex_input_binding()
|
|
{
|
|
VkVertexInputBindingDescription vi_binding = {};
|
|
vi_binding.binding = 0;
|
|
vi_binding.stride = vertex_stride();
|
|
vi_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
|
|
|
return vi_binding;
|
|
}
|
|
|
|
static std::vector<VkVertexInputAttributeDescription> vertex_input_attributes()
|
|
{
|
|
std::vector<VkVertexInputAttributeDescription> vi_attrs(2);
|
|
// Position
|
|
vi_attrs[0].location = 0;
|
|
vi_attrs[0].binding = 0;
|
|
vi_attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
|
vi_attrs[0].offset = 0;
|
|
// Normal
|
|
vi_attrs[1].location = 1;
|
|
vi_attrs[1].binding = 0;
|
|
vi_attrs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
|
|
vi_attrs[1].offset = sizeof(float) * 3;
|
|
|
|
return vi_attrs;
|
|
}
|
|
|
|
static VkIndexType index_type()
|
|
{
|
|
return VK_INDEX_TYPE_UINT32;
|
|
}
|
|
|
|
static VkPipelineInputAssemblyStateCreateInfo input_assembly_state()
|
|
{
|
|
VkPipelineInputAssemblyStateCreateInfo ia_info = {};
|
|
ia_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
|
ia_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
ia_info.primitiveRestartEnable = false;
|
|
return ia_info;
|
|
}
|
|
|
|
void build(const std::vector<std::array<float, 6>> &vertices, const std::vector<std::array<int, 3>> &faces)
|
|
{
|
|
positions_.reserve(vertices.size());
|
|
normals_.reserve(vertices.size());
|
|
for (const auto &v : vertices) {
|
|
positions_.emplace_back(Position{ v[0], v[1], v[2] });
|
|
normals_.emplace_back(Normal{ v[3], v[4], v[5] });
|
|
}
|
|
|
|
faces_.reserve(faces.size());
|
|
for (const auto &f : faces)
|
|
faces_.emplace_back(Face{ f[0], f[1], f[2] });
|
|
}
|
|
|
|
uint32_t vertex_count() const
|
|
{
|
|
return static_cast<uint32_t>(positions_.size());
|
|
}
|
|
|
|
VkDeviceSize vertex_buffer_size() const
|
|
{
|
|
return vertex_stride() * vertex_count();
|
|
}
|
|
|
|
void vertex_buffer_write(void *data) const
|
|
{
|
|
float *dst = reinterpret_cast<float *>(data);
|
|
for (size_t i = 0; i < positions_.size(); i++) {
|
|
const Position &pos = positions_[i];
|
|
const Normal &normal = normals_[i];
|
|
dst[0] = pos.x;
|
|
dst[1] = pos.y;
|
|
dst[2] = pos.z;
|
|
dst[3] = normal.x;
|
|
dst[4] = normal.y;
|
|
dst[5] = normal.z;
|
|
dst += 6;
|
|
}
|
|
}
|
|
|
|
uint32_t index_count() const
|
|
{
|
|
return static_cast<uint32_t>(faces_.size()) * 3;
|
|
}
|
|
|
|
VkDeviceSize index_buffer_size() const
|
|
{
|
|
return sizeof(uint32_t) * index_count();
|
|
}
|
|
|
|
void index_buffer_write(void *data) const
|
|
{
|
|
uint32_t *dst = reinterpret_cast<uint32_t *>(data);
|
|
for (const auto &face : faces_) {
|
|
dst[0] = face.v0;
|
|
dst[1] = face.v1;
|
|
dst[2] = face.v2;
|
|
dst += 3;
|
|
}
|
|
}
|
|
|
|
std::vector<Position> positions_;
|
|
std::vector<Normal> normals_;
|
|
std::vector<Face> faces_;
|
|
};
|
|
|
|
class BuildPyramid {
|
|
public:
|
|
BuildPyramid(Mesh &mesh)
|
|
{
|
|
const std::vector<std::array<float, 6>> vertices = {
|
|
// position normal
|
|
{ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f },
|
|
{ -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f },
|
|
{ 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f },
|
|
{ 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f },
|
|
{ -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f },
|
|
};
|
|
|
|
const std::vector<std::array<int, 3>> faces = {
|
|
{ 0, 1, 2 },
|
|
{ 0, 2, 3 },
|
|
{ 0, 3, 4 },
|
|
{ 0, 4, 1 },
|
|
{ 1, 4, 3 },
|
|
{ 1, 3, 2 },
|
|
};
|
|
|
|
mesh.build(vertices, faces);
|
|
}
|
|
};
|
|
|
|
class BuildIcosphere {
|
|
public:
|
|
BuildIcosphere(Mesh &mesh) : mesh_(mesh), radius_(1.0f)
|
|
{
|
|
const int tessellate_level = 2;
|
|
|
|
build_icosahedron();
|
|
for (int i = 0; i < tessellate_level; i++)
|
|
tessellate();
|
|
}
|
|
|
|
private:
|
|
void build_icosahedron()
|
|
{
|
|
// https://en.wikipedia.org/wiki/Regular_icosahedron
|
|
const float l1 = std::sqrt(2.0f / (5.0f + std::sqrt(5.0f))) * radius_;
|
|
const float l2 = std::sqrt(2.0f / (5.0f - std::sqrt(5.0f))) * radius_;
|
|
// vertices are from three golden rectangles
|
|
const std::vector<std::array<float, 6>> icosahedron_vertices = {
|
|
// position normal
|
|
{ -l1, -l2, 0.0f, -l1, -l2, 0.0f, },
|
|
{ l1, -l2, 0.0f, l1, -l2, 0.0f, },
|
|
{ l1, l2, 0.0f, l1, l2, 0.0f, },
|
|
{ -l1, l2, 0.0f, -l1, l2, 0.0f, },
|
|
|
|
{ -l2, 0.0f, -l1, -l2, 0.0f, -l1, },
|
|
{ l2, 0.0f, -l1, l2, 0.0f, -l1, },
|
|
{ l2, 0.0f, l1, l2, 0.0f, l1, },
|
|
{ -l2, 0.0f, l1, -l2, 0.0f, l1, },
|
|
|
|
{ 0.0f, -l1, -l2, 0.0f, -l1, -l2, },
|
|
{ 0.0f, l1, -l2, 0.0f, l1, -l2, },
|
|
{ 0.0f, l1, l2, 0.0f, l1, l2, },
|
|
{ 0.0f, -l1, l2, 0.0f, -l1, l2, },
|
|
};
|
|
const std::vector<std::array<int, 3>> icosahedron_faces = {
|
|
// triangles sharing vertex 0
|
|
{ 0, 1, 11 },
|
|
{ 0, 11, 7 },
|
|
{ 0, 7, 4 },
|
|
{ 0, 4, 8 },
|
|
{ 0, 8, 1 },
|
|
// adjacent triangles
|
|
{ 11, 1, 6 },
|
|
{ 7, 11, 10 },
|
|
{ 4, 7, 3 },
|
|
{ 8, 4, 9 },
|
|
{ 1, 8, 5 },
|
|
// triangles sharing vertex 2
|
|
{ 2, 3, 10 },
|
|
{ 2, 10, 6 },
|
|
{ 2, 6, 5 },
|
|
{ 2, 5, 9 },
|
|
{ 2, 9, 3 },
|
|
// adjacent triangles
|
|
{ 10, 3, 7 },
|
|
{ 6, 10, 11 },
|
|
{ 5, 6, 1 },
|
|
{ 9, 5, 8 },
|
|
{ 3, 9, 4 },
|
|
};
|
|
|
|
mesh_.build(icosahedron_vertices, icosahedron_faces);
|
|
}
|
|
|
|
void tessellate()
|
|
{
|
|
size_t middle_point_count = mesh_.faces_.size() * 3 / 2;
|
|
size_t final_face_count = mesh_.faces_.size() * 4;
|
|
|
|
std::vector<Mesh::Face> faces;
|
|
faces.reserve(final_face_count);
|
|
|
|
middle_points_.clear();
|
|
middle_points_.reserve(middle_point_count);
|
|
|
|
mesh_.positions_.reserve(mesh_.vertex_count() + middle_point_count);
|
|
mesh_.normals_.reserve(mesh_.vertex_count() + middle_point_count);
|
|
|
|
for (const auto &f : mesh_.faces_) {
|
|
int v0 = f.v0;
|
|
int v1 = f.v1;
|
|
int v2 = f.v2;
|
|
|
|
int v01 = add_middle_point(v0, v1);
|
|
int v12 = add_middle_point(v1, v2);
|
|
int v20 = add_middle_point(v2, v0);
|
|
|
|
faces.emplace_back(Mesh::Face{ v0, v01, v20 });
|
|
faces.emplace_back(Mesh::Face{ v1, v12, v01 });
|
|
faces.emplace_back(Mesh::Face{ v2, v20, v12 });
|
|
faces.emplace_back(Mesh::Face{ v01, v12, v20 });
|
|
}
|
|
|
|
mesh_.faces_.swap(faces);
|
|
}
|
|
|
|
int add_middle_point(int a, int b)
|
|
{
|
|
uint64_t key = (a < b) ? ((uint64_t) a << 32 | b) : ((uint64_t) b << 32 | a);
|
|
auto it = middle_points_.find(key);
|
|
if (it != middle_points_.end())
|
|
return it->second;
|
|
|
|
const Mesh::Position &pos_a = mesh_.positions_[a];
|
|
const Mesh::Position &pos_b = mesh_.positions_[b];
|
|
Mesh::Position pos_mid = {
|
|
(pos_a.x + pos_b.x) / 2.0f,
|
|
(pos_a.y + pos_b.y) / 2.0f,
|
|
(pos_a.z + pos_b.z) / 2.0f,
|
|
};
|
|
float scale = radius_ / std::sqrt(pos_mid.x * pos_mid.x +
|
|
pos_mid.y * pos_mid.y +
|
|
pos_mid.z * pos_mid.z);
|
|
pos_mid.x *= scale;
|
|
pos_mid.y *= scale;
|
|
pos_mid.z *= scale;
|
|
|
|
Mesh::Normal normal_mid = { pos_mid.x, pos_mid.y, pos_mid.z };
|
|
normal_mid.x /= radius_;
|
|
normal_mid.y /= radius_;
|
|
normal_mid.z /= radius_;
|
|
|
|
mesh_.positions_.emplace_back(pos_mid);
|
|
mesh_.normals_.emplace_back(normal_mid);
|
|
|
|
int mid = mesh_.vertex_count() - 1;
|
|
middle_points_.emplace(std::make_pair(key, mid));
|
|
|
|
return mid;
|
|
}
|
|
|
|
Mesh &mesh_;
|
|
const float radius_;
|
|
std::unordered_map<uint64_t, uint32_t> middle_points_;
|
|
};
|
|
|
|
class BuildTeapot {
|
|
public:
|
|
BuildTeapot(Mesh &mesh)
|
|
{
|
|
#include "Meshes.teapot.h"
|
|
const int position_count = sizeof(teapot_positions) / sizeof(teapot_positions[0]);
|
|
const int index_count = sizeof(teapot_indices) / sizeof(teapot_indices[0]);
|
|
assert(position_count % 3 == 0 && index_count % 3 == 0);
|
|
|
|
Mesh::Position translate;
|
|
float scale;
|
|
get_transform(teapot_positions, position_count, translate, scale);
|
|
|
|
for (int i = 0; i < position_count; i += 3) {
|
|
mesh.positions_.emplace_back(Mesh::Position{
|
|
(teapot_positions[i + 0] + translate.x) * scale,
|
|
(teapot_positions[i + 1] + translate.y) * scale,
|
|
(teapot_positions[i + 2] + translate.z) * scale,
|
|
});
|
|
|
|
mesh.normals_.emplace_back(Mesh::Normal{
|
|
teapot_normals[i + 0],
|
|
teapot_normals[i + 1],
|
|
teapot_normals[i + 2],
|
|
});
|
|
}
|
|
|
|
for (int i = 0; i < index_count; i += 3) {
|
|
mesh.faces_.emplace_back(Mesh::Face{
|
|
teapot_indices[i + 0],
|
|
teapot_indices[i + 1],
|
|
teapot_indices[i + 2]
|
|
});
|
|
}
|
|
}
|
|
|
|
void get_transform(const float *positions, int position_count,
|
|
Mesh::Position &translate, float &scale)
|
|
{
|
|
float min[3] = {
|
|
positions[0],
|
|
positions[1],
|
|
positions[2],
|
|
};
|
|
float max[3] = {
|
|
positions[0],
|
|
positions[1],
|
|
positions[2],
|
|
};
|
|
for (int i = 3; i < position_count; i += 3) {
|
|
for (int j = 0; j < 3; j++) {
|
|
if (min[j] > positions[i + j])
|
|
min[j] = positions[i + j];
|
|
if (max[j] < positions[i + j])
|
|
max[j] = positions[i + j];
|
|
}
|
|
}
|
|
|
|
translate.x = -(min[0] + max[0]) / 2.0f;
|
|
translate.y = -(min[1] + max[1]) / 2.0f;
|
|
translate.z = -(min[2] + max[2]) / 2.0f;
|
|
|
|
float extents[3] = {
|
|
max[0] + translate.x,
|
|
max[1] + translate.y,
|
|
max[2] + translate.z,
|
|
};
|
|
|
|
float max_extent = extents[0];
|
|
if (max_extent < extents[1])
|
|
max_extent = extents[1];
|
|
if (max_extent < extents[2])
|
|
max_extent = extents[2];
|
|
|
|
scale = 1.0f / max_extent;
|
|
}
|
|
};
|
|
|
|
void build_meshes(std::array<Mesh, Meshes::MESH_COUNT> &meshes)
|
|
{
|
|
BuildPyramid build_pyramid(meshes[Meshes::MESH_PYRAMID]);
|
|
BuildIcosphere build_icosphere(meshes[Meshes::MESH_ICOSPHERE]);
|
|
BuildTeapot build_teapot(meshes[Meshes::MESH_TEAPOT]);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Meshes::Meshes(VkDevice dev, const std::vector<VkMemoryPropertyFlags> &mem_flags)
|
|
: dev_(dev),
|
|
vertex_input_binding_(Mesh::vertex_input_binding()),
|
|
vertex_input_attrs_(Mesh::vertex_input_attributes()),
|
|
vertex_input_state_(),
|
|
input_assembly_state_(Mesh::input_assembly_state()),
|
|
index_type_(Mesh::index_type())
|
|
{
|
|
vertex_input_state_.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
|
vertex_input_state_.vertexBindingDescriptionCount = 1;
|
|
vertex_input_state_.pVertexBindingDescriptions = &vertex_input_binding_;
|
|
vertex_input_state_.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attrs_.size());
|
|
vertex_input_state_.pVertexAttributeDescriptions = vertex_input_attrs_.data();
|
|
|
|
std::array<Mesh, MESH_COUNT> meshes;
|
|
build_meshes(meshes);
|
|
|
|
draw_commands_.reserve(meshes.size());
|
|
uint32_t first_index = 0;
|
|
int32_t vertex_offset = 0;
|
|
VkDeviceSize vb_size = 0;
|
|
VkDeviceSize ib_size = 0;
|
|
for (const auto &mesh : meshes) {
|
|
VkDrawIndexedIndirectCommand draw = {};
|
|
draw.indexCount = mesh.index_count();
|
|
draw.instanceCount = 1;
|
|
draw.firstIndex = first_index;
|
|
draw.vertexOffset = vertex_offset;
|
|
draw.firstInstance = 0;
|
|
|
|
draw_commands_.push_back(draw);
|
|
|
|
first_index += mesh.index_count();
|
|
vertex_offset += mesh.vertex_count();
|
|
vb_size += mesh.vertex_buffer_size();
|
|
ib_size += mesh.index_buffer_size();
|
|
}
|
|
|
|
allocate_resources(vb_size, ib_size, mem_flags);
|
|
|
|
uint8_t *vb_data, *ib_data;
|
|
vk::assert_success(vk::MapMemory(dev_, mem_, 0, VK_WHOLE_SIZE,
|
|
0, reinterpret_cast<void **>(&vb_data)));
|
|
ib_data = vb_data + ib_mem_offset_;
|
|
|
|
for (const auto &mesh : meshes) {
|
|
mesh.vertex_buffer_write(vb_data);
|
|
mesh.index_buffer_write(ib_data);
|
|
vb_data += mesh.vertex_buffer_size();
|
|
ib_data += mesh.index_buffer_size();
|
|
}
|
|
|
|
vk::UnmapMemory(dev_, mem_);
|
|
}
|
|
|
|
Meshes::~Meshes()
|
|
{
|
|
vk::FreeMemory(dev_, mem_, nullptr);
|
|
vk::DestroyBuffer(dev_, vb_, nullptr);
|
|
vk::DestroyBuffer(dev_, ib_, nullptr);
|
|
}
|
|
|
|
void Meshes::cmd_bind_buffers(VkCommandBuffer cmd) const
|
|
{
|
|
const VkDeviceSize vb_offset = 0;
|
|
vk::CmdBindVertexBuffers(cmd, 0, 1, &vb_, &vb_offset);
|
|
|
|
vk::CmdBindIndexBuffer(cmd, ib_, 0, index_type_);
|
|
}
|
|
|
|
void Meshes::cmd_draw(VkCommandBuffer cmd, Type type) const
|
|
{
|
|
const auto &draw = draw_commands_[type];
|
|
vk::CmdDrawIndexed(cmd, draw.indexCount, draw.instanceCount,
|
|
draw.firstIndex, draw.vertexOffset, draw.firstInstance);
|
|
}
|
|
|
|
void Meshes::allocate_resources(VkDeviceSize vb_size, VkDeviceSize ib_size, const std::vector<VkMemoryPropertyFlags> &mem_flags)
|
|
{
|
|
VkBufferCreateInfo buf_info = {};
|
|
buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
buf_info.size = vb_size;
|
|
buf_info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
|
buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
vk::CreateBuffer(dev_, &buf_info, nullptr, &vb_);
|
|
|
|
buf_info.size = ib_size;
|
|
buf_info.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
|
vk::CreateBuffer(dev_, &buf_info, nullptr, &ib_);
|
|
|
|
VkMemoryRequirements vb_mem_reqs, ib_mem_reqs;
|
|
vk::GetBufferMemoryRequirements(dev_, vb_, &vb_mem_reqs);
|
|
vk::GetBufferMemoryRequirements(dev_, ib_, &ib_mem_reqs);
|
|
|
|
// indices follow vertices
|
|
ib_mem_offset_ = vb_mem_reqs.size +
|
|
(ib_mem_reqs.alignment - (vb_mem_reqs.size % ib_mem_reqs.alignment));
|
|
|
|
VkMemoryAllocateInfo mem_info = {};
|
|
mem_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
mem_info.allocationSize = ib_mem_offset_ + ib_mem_reqs.size;
|
|
|
|
// find any supported and mappable memory type
|
|
uint32_t mem_types = (vb_mem_reqs.memoryTypeBits & ib_mem_reqs.memoryTypeBits);
|
|
for (uint32_t idx = 0; idx < mem_flags.size(); idx++) {
|
|
if ((mem_types & (1 << idx)) &&
|
|
(mem_flags[idx] & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
|
|
(mem_flags[idx] & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
|
|
// TODO this may not be reachable
|
|
mem_info.memoryTypeIndex = idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
vk::AllocateMemory(dev_, &mem_info, nullptr, &mem_);
|
|
|
|
vk::BindBufferMemory(dev_, vb_, mem_, 0);
|
|
vk::BindBufferMemory(dev_, ib_, mem_, ib_mem_offset_);
|
|
}
|