More shapes

This commit is contained in:
Brad Davis 2016-05-22 17:41:50 -07:00
parent cd1e910844
commit 2c703e963c
16 changed files with 895 additions and 780 deletions

View file

@ -30,7 +30,7 @@ static GeometryCache::Shape MAPPING[entity::NUM_SHAPES] = {
GeometryCache::Cube,
GeometryCache::Sphere,
GeometryCache::Tetrahedron,
GeometryCache::Octahetron,
GeometryCache::Octahedron,
GeometryCache::Dodecahedron,
GeometryCache::Icosahedron,
GeometryCache::Torus,
@ -93,7 +93,7 @@ void RenderableShapeEntityItem::render(RenderArgs* args) {
if (!success) {
return;
}
if (_shape != entity::Cube) {
if (_shape == entity::Sphere) {
modelTransform.postScale(SPHERE_ENTITY_SCALE);
}
batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation

View file

@ -27,7 +27,7 @@ namespace entity {
"Cube",
"Sphere",
"Tetrahedron",
"Octahetron",
"Octahedron",
"Dodecahedron",
"Icosahedron",
"Torus",

View file

@ -19,7 +19,7 @@ namespace entity {
Cube,
Sphere,
Tetrahedron,
Octahetron,
Octahedron,
Dodecahedron,
Icosahedron,
Torus,

View file

@ -51,8 +51,8 @@ static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT;
static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals
static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3);
static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT16;
static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint16);
static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT32;
static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint32);
void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices) {
vertexBuffer->append(vertices);
@ -112,102 +112,7 @@ void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count
}
}
// The golden ratio
static const float PHI = 1.61803398874f;
const VertexVector& icosahedronVertices() {
static const float a = 1;
static const float b = PHI / 2.0f;
static const VertexVector vertices{ //
vec3(0, b, -a), vec3(-b, a, 0), vec3(b, a, 0), //
vec3(0, b, a), vec3(b, a, 0), vec3(-b, a, 0), //
vec3(0, b, a), vec3(-a, 0, b), vec3(0, -b, a), //
vec3(0, b, a), vec3(0, -b, a), vec3(a, 0, b), //
vec3(0, b, -a), vec3(a, 0, -b), vec3(0, -b, -a),//
vec3(0, b, -a), vec3(0, -b, -a), vec3(-a, 0, -b), //
vec3(0, -b, a), vec3(-b, -a, 0), vec3(b, -a, 0), //
vec3(0, -b, -a), vec3(b, -a, 0), vec3(-b, -a, 0), //
vec3(-b, a, 0), vec3(-a, 0, -b), vec3(-a, 0, b), //
vec3(-b, -a, 0), vec3(-a, 0, b), vec3(-a, 0, -b), //
vec3(b, a, 0), vec3(a, 0, b), vec3(a, 0, -b), //
vec3(b, -a, 0), vec3(a, 0, -b), vec3(a, 0, b), //
vec3(0, b, a), vec3(-b, a, 0), vec3(-a, 0, b), //
vec3(0, b, a), vec3(a, 0, b), vec3(b, a, 0), //
vec3(0, b, -a), vec3(-a, 0, -b), vec3(-b, a, 0), //
vec3(0, b, -a), vec3(b, a, 0), vec3(a, 0, -b), //
vec3(0, -b, -a), vec3(-b, -a, 0), vec3(-a, 0, -b), //
vec3(0, -b, -a), vec3(a, 0, -b), vec3(b, -a, 0), //
vec3(0, -b, a), vec3(-a, 0, b), vec3(-b, -a, 0), //
vec3(0, -b, a), vec3(b, -a, 0), vec3(a, 0, b)
}; //
return vertices;
}
const VertexVector& tetrahedronVertices() {
static const auto A = vec3(1, 1, 1);
static const auto B = vec3(1, -1, -1);
static const auto C = vec3(-1, 1, -1);
static const auto D = vec3(-1, -1, 1);
static const VertexVector vertices{
A, B, C,
D, B, A,
C, D, A,
C, B, D,
};
return vertices;
}
static const size_t TESSELTATION_MULTIPLIER = 4;
static const size_t ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT = 3;
static const size_t VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER = 2;
VertexVector tesselate(const VertexVector& startingTriangles, int count) {
VertexVector triangles = startingTriangles;
if (0 != (triangles.size() % 3)) {
throw std::runtime_error("Bad number of vertices for tesselation");
}
for (size_t i = 0; i < triangles.size(); ++i) {
triangles[i] = glm::normalize(triangles[i]);
}
VertexVector newTriangles;
while (count) {
newTriangles.clear();
// Tesselation takes one triangle and makes it into 4 triangles
// See https://en.wikipedia.org/wiki/Space-filling_tree#/media/File:Space_Filling_Tree_Tri_iter_1_2_3.png
newTriangles.reserve(triangles.size() * TESSELTATION_MULTIPLIER);
for (size_t i = 0; i < triangles.size(); i += VERTICES_PER_TRIANGLE) {
const vec3& a = triangles[i];
const vec3& b = triangles[i + 1];
const vec3& c = triangles[i + 2];
vec3 ab = glm::normalize(a + b);
vec3 bc = glm::normalize(b + c);
vec3 ca = glm::normalize(c + a);
newTriangles.push_back(a);
newTriangles.push_back(ab);
newTriangles.push_back(ca);
newTriangles.push_back(b);
newTriangles.push_back(bc);
newTriangles.push_back(ab);
newTriangles.push_back(c);
newTriangles.push_back(ca);
newTriangles.push_back(bc);
newTriangles.push_back(ab);
newTriangles.push_back(bc);
newTriangles.push_back(ca);
}
triangles.swap(newTriangles);
--count;
}
return triangles;
}
size_t GeometryCache::getShapeTriangleCount(Shape shape) {
return _shapes[shape]._indexCount / VERTICES_PER_TRIANGLE;
@ -221,6 +126,324 @@ size_t GeometryCache::getCubeTriangleCount() {
return getShapeTriangleCount(Cube);
}
using Index = uint32_t;
using IndexPair = uint64_t;
using IndexPairs = std::unordered_set<IndexPair>;
template <size_t N>
using Face = std::array<Index, N>;
template <size_t N>
using FaceVector = std::vector<Face<N>>;
template <size_t N>
struct Solid {
VertexVector vertices;
FaceVector<N> faces;
Solid<N>& fitDimension(float newMaxDimension) {
float maxDimension = 0;
for (const auto& vertex : vertices) {
maxDimension = std::max(maxDimension, std::max(std::max(vertex.x, vertex.y), vertex.z));
}
float multiplier = newMaxDimension / maxDimension;
for (auto& vertex : vertices) {
vertex *= multiplier;
}
return *this;
}
vec3 getFaceNormal(size_t faceIndex) const {
vec3 result;
const auto& face = faces[faceIndex];
for (size_t i = 0; i < N; ++i) {
result += vertices[face[i]];
}
result /= N;
return glm::normalize(result);
}
};
template <size_t N>
static size_t triangulatedFaceTriangleCount() {
return N - 2;
}
template <size_t N>
static size_t triangulatedFaceIndexCount() {
return triangulatedFaceTriangleCount<N>() * VERTICES_PER_TRIANGLE;
}
static IndexPair indexToken(Index a, Index b) {
if (a > b) {
std::swap(a, b);
}
return (((IndexPair)a) << 32) | ((IndexPair)b);
}
static Solid<3> tesselate(Solid<3> solid, int count) {
float length = glm::length(solid.vertices[0]);
for (int i = 0; i < count; ++i) {
Solid<3> result { solid.vertices, {} };
result.vertices.reserve(solid.vertices.size() + solid.faces.size() * 3);
for (size_t f = 0; f < solid.faces.size(); ++f) {
Index baseVertex = (Index)result.vertices.size();
const Face<3>& oldFace = solid.faces[f];
const vec3& a = solid.vertices[oldFace[0]];
const vec3& b = solid.vertices[oldFace[1]];
const vec3& c = solid.vertices[oldFace[2]];
vec3 ab = glm::normalize(a + b) * length;
vec3 bc = glm::normalize(b + c) * length;
vec3 ca = glm::normalize(c + a) * length;
result.vertices.push_back(ab);
result.vertices.push_back(bc);
result.vertices.push_back(ca);
result.faces.push_back(Face<3>{ { oldFace[0], baseVertex, baseVertex + 2 } });
result.faces.push_back(Face<3>{ { baseVertex, oldFace[1], baseVertex + 1 } });
result.faces.push_back(Face<3>{ { baseVertex + 1, oldFace[2], baseVertex + 2 } });
result.faces.push_back(Face<3>{ { baseVertex, baseVertex + 1, baseVertex + 2 } });
}
solid = result;
}
return solid;
}
template <size_t N>
void setupFlatShape(GeometryCache::ShapeData& shapeData, const Solid<N>& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) {
Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE);
VertexVector vertices;
IndexVector solidIndices, wireIndices;
IndexPairs wireSeenIndices;
size_t faceCount = shape.faces.size();
size_t faceIndexCount = triangulatedFaceIndexCount<N>();
vertices.reserve(N * faceCount * 2);
solidIndices.reserve(faceIndexCount * faceCount);
for (size_t f = 0; f < faceCount; ++f) {
const Face<N>& face = shape.faces[f];
// Compute the face normal
vec3 faceNormal = shape.getFaceNormal(f);
// Create the vertices for the face
for (Index i = 0; i < N; ++i) {
Index originalIndex = face[i];
vertices.push_back(shape.vertices[originalIndex]);
vertices.push_back(faceNormal);
}
// Create the wire indices for unseen edges
for (Index i = 0; i < N; ++i) {
Index a = i;
Index b = (i + 1) % N;
auto token = indexToken(face[a], face[b]);
if (0 == wireSeenIndices.count(token)) {
wireSeenIndices.insert(token);
wireIndices.push_back(a + baseVertex);
wireIndices.push_back(b + baseVertex);
}
}
// Create the solid face indices
for (Index i = 0; i < N - 2; ++i) {
solidIndices.push_back(0 + baseVertex);
solidIndices.push_back(i + 1 + baseVertex);
solidIndices.push_back(i + 2 + baseVertex);
}
baseVertex += (Index)N;
}
shapeData.setupVertices(vertexBuffer, vertices);
shapeData.setupIndices(indexBuffer, solidIndices, wireIndices);
}
template <size_t N>
void setupSmoothShape(GeometryCache::ShapeData& shapeData, const Solid<N>& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) {
Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE);
VertexVector vertices;
vertices.reserve(shape.vertices.size() * 2);
for (const auto& vertex : shape.vertices) {
vertices.push_back(vertex);
vertices.push_back(vertex);
}
IndexVector solidIndices, wireIndices;
IndexPairs wireSeenIndices;
size_t faceCount = shape.faces.size();
size_t faceIndexCount = triangulatedFaceIndexCount<N>();
solidIndices.reserve(faceIndexCount * faceCount);
for (size_t f = 0; f < faceCount; ++f) {
const Face<N>& face = shape.faces[f];
// Create the wire indices for unseen edges
for (Index i = 0; i < N; ++i) {
Index a = face[i];
Index b = face[(i + 1) % N];
auto token = indexToken(a, b);
if (0 == wireSeenIndices.count(token)) {
wireSeenIndices.insert(token);
wireIndices.push_back(a + baseVertex);
wireIndices.push_back(b + baseVertex);
}
}
// Create the solid face indices
for (Index i = 0; i < N - 2; ++i) {
solidIndices.push_back(face[i] + baseVertex);
solidIndices.push_back(face[i + 1] + baseVertex);
solidIndices.push_back(face[i + 2] + baseVertex);
}
}
shapeData.setupVertices(vertexBuffer, vertices);
shapeData.setupIndices(indexBuffer, solidIndices, wireIndices);
}
// The golden ratio
static const float PHI = 1.61803398874f;
static const Solid<3>& tetrahedron() {
static const auto A = vec3(1, 1, 1);
static const auto B = vec3(1, -1, -1);
static const auto C = vec3(-1, 1, -1);
static const auto D = vec3(-1, -1, 1);
static const Solid<3> TETRAHEDRON = Solid<3>{
{ A, B, C, D },
FaceVector<3>{
Face<3> { { 0, 1, 2 } },
Face<3> { { 3, 1, 0 } },
Face<3> { { 2, 3, 0 } },
Face<3> { { 2, 1, 3 } },
}
}.fitDimension(0.5f);
return TETRAHEDRON;
}
static const Solid<4>& cube() {
static const auto A = vec3(1, 1, 1);
static const auto B = vec3(-1, 1, 1);
static const auto C = vec3(-1, 1, -1);
static const auto D = vec3(1, 1, -1);
static const Solid<4> CUBE = Solid<4>{
{ A, B, C, D, -A, -B, -C, -D },
FaceVector<4>{
Face<4> { { 3, 2, 1, 0 } },
Face<4> { { 0, 1, 7, 6 } },
Face<4> { { 1, 2, 4, 7 } },
Face<4> { { 2, 3, 5, 4 } },
Face<4> { { 3, 0, 6, 5 } },
Face<4> { { 4, 5, 6, 7 } },
}
}.fitDimension(0.5f);
return CUBE;
}
static const Solid<3>& octahedron() {
static const auto A = vec3(0, 1, 0);
static const auto B = vec3(0, -1, 0);
static const auto C = vec3(0, 0, 1);
static const auto D = vec3(0, 0, -1);
static const auto E = vec3(1, 0, 0);
static const auto F = vec3(-1, 0, 0);
static const Solid<3> OCTAHEDRON = Solid<3>{
{ A, B, C, D, E, F},
FaceVector<3> {
Face<3> { { 0, 2, 4, } },
Face<3> { { 0, 4, 3, } },
Face<3> { { 0, 3, 5, } },
Face<3> { { 0, 5, 2, } },
Face<3> { { 1, 4, 2, } },
Face<3> { { 1, 3, 4, } },
Face<3> { { 1, 5, 3, } },
Face<3> { { 1, 2, 5, } },
}
}.fitDimension(0.5f);
return OCTAHEDRON;
}
static const Solid<5>& dodecahedron() {
static const float P = PHI;
static const float IP = 1.0f / PHI;
static const vec3 A = vec3(IP, P, 0);
static const vec3 B = vec3(-IP, P, 0);
static const vec3 C = vec3(-1, 1, 1);
static const vec3 D = vec3(0, IP, P);
static const vec3 E = vec3(1, 1, 1);
static const vec3 F = vec3(1, 1, -1);
static const vec3 G = vec3(-1, 1, -1);
static const vec3 H = vec3(-P, 0, IP);
static const vec3 I = vec3(0, -IP, P);
static const vec3 J = vec3(P, 0, IP);
static const Solid<5> DODECAHEDRON = Solid<5>{
{
A, B, C, D, E, F, G, H, I, J,
-A, -B, -C, -D, -E, -F, -G, -H, -I, -J,
},
FaceVector<5> {
Face<5> { { 0, 1, 2, 3, 4 } },
Face<5> { { 0, 5, 18, 6, 1 } },
Face<5> { { 1, 6, 19, 7, 2 } },
Face<5> { { 2, 7, 15, 8, 3 } },
Face<5> { { 3, 8, 16, 9, 4 } },
Face<5> { { 4, 9, 17, 5, 0 } },
Face<5> { { 14, 13, 12, 11, 10 } },
Face<5> { { 11, 16, 8, 15, 10 } },
Face<5> { { 12, 17, 9, 16, 11 } },
Face<5> { { 13, 18, 5, 17, 12 } },
Face<5> { { 14, 19, 6, 18, 13 } },
Face<5> { { 10, 15, 7, 19, 14 } },
}
}.fitDimension(0.5f);
return DODECAHEDRON;
}
static const Solid<3>& icosahedron() {
static const float N = 1.0f / PHI;
static const float P = 1.0f;
static const auto A = vec3(N, P, 0);
static const auto B = vec3(-N, P, 0);
static const auto C = vec3(0, N, P);
static const auto D = vec3(P, 0, N);
static const auto E = vec3(P, 0, -N);
static const auto F = vec3(0, N, -P);
static const Solid<3> ICOSAHEDRON = Solid<3> {
{
A, B, C, D, E, F,
-A, -B, -C, -D, -E, -F,
},
FaceVector<3> {
Face<3> { { 1, 2, 0 } },
Face<3> { { 2, 3, 0 } },
Face<3> { { 3, 4, 0 } },
Face<3> { { 4, 5, 0 } },
Face<3> { { 5, 1, 0 } },
Face<3> { { 1, 10, 2 } },
Face<3> { { 11, 2, 10 } },
Face<3> { { 2, 11, 3 } },
Face<3> { { 7, 3, 11 } },
Face<3> { { 3, 7, 4 } },
Face<3> { { 8, 4, 7 } },
Face<3> { { 4, 8, 5 } },
Face<3> { { 9, 5, 8 } },
Face<3> { { 5, 9, 1 } },
Face<3> { { 10, 1, 9 } },
Face<3> { { 8, 7, 6 } },
Face<3> { { 9, 8, 6 } },
Face<3> { { 10, 9, 6 } },
Face<3> { { 11, 10, 6 } },
Face<3> { { 7, 11, 6 } },
}
}.fitDimension(0.5f);
return ICOSAHEDRON;
}
// FIXME solids need per-face vertices, but smooth shaded
// components do not. Find a way to support using draw elements
@ -230,229 +453,28 @@ size_t GeometryCache::getCubeTriangleCount() {
void GeometryCache::buildShapes() {
auto vertexBuffer = std::make_shared<gpu::Buffer>();
auto indexBuffer = std::make_shared<gpu::Buffer>();
size_t startingIndex = 0;
// Cube
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
{
ShapeData& shapeData = _shapes[Cube];
VertexVector vertices;
// front
vertices.push_back(vec3(1, 1, 1));
vertices.push_back(vec3(0, 0, 1));
vertices.push_back(vec3(-1, 1, 1));
vertices.push_back(vec3(0, 0, 1));
vertices.push_back(vec3(-1, -1, 1));
vertices.push_back(vec3(0, 0, 1));
vertices.push_back(vec3(1, -1, 1));
vertices.push_back(vec3(0, 0, 1));
// right
vertices.push_back(vec3(1, 1, 1));
vertices.push_back(vec3(1, 0, 0));
vertices.push_back(vec3(1, -1, 1));
vertices.push_back(vec3(1, 0, 0));
vertices.push_back(vec3(1, -1, -1));
vertices.push_back(vec3(1, 0, 0));
vertices.push_back(vec3(1, 1, -1));
vertices.push_back(vec3(1, 0, 0));
// top
vertices.push_back(vec3(1, 1, 1));
vertices.push_back(vec3(0, 1, 0));
vertices.push_back(vec3(1, 1, -1));
vertices.push_back(vec3(0, 1, 0));
vertices.push_back(vec3(-1, 1, -1));
vertices.push_back(vec3(0, 1, 0));
vertices.push_back(vec3(-1, 1, 1));
vertices.push_back(vec3(0, 1, 0));
// left
vertices.push_back(vec3(-1, 1, 1));
vertices.push_back(vec3(-1, 0, 0));
vertices.push_back(vec3(-1, 1, -1));
vertices.push_back(vec3(-1, 0, 0));
vertices.push_back(vec3(-1, -1, -1));
vertices.push_back(vec3(-1, 0, 0));
vertices.push_back(vec3(-1, -1, 1));
vertices.push_back(vec3(-1, 0, 0));
// bottom
vertices.push_back(vec3(-1, -1, -1));
vertices.push_back(vec3(0, -1, 0));
vertices.push_back(vec3(1, -1, -1));
vertices.push_back(vec3(0, -1, 0));
vertices.push_back(vec3(1, -1, 1));
vertices.push_back(vec3(0, -1, 0));
vertices.push_back(vec3(-1, -1, 1));
vertices.push_back(vec3(0, -1, 0));
// back
vertices.push_back(vec3(1, -1, -1));
vertices.push_back(vec3(0, 0, -1));
vertices.push_back(vec3(-1, -1, -1));
vertices.push_back(vec3(0, 0, -1));
vertices.push_back(vec3(-1, 1, -1));
vertices.push_back(vec3(0, 0, -1));
vertices.push_back(vec3(1, 1, -1));
vertices.push_back(vec3(0, 0, -1));
static const size_t VERTEX_FORMAT_SIZE = 2;
static const size_t VERTEX_OFFSET = 0;
for (size_t i = 0; i < vertices.size(); ++i) {
auto vertexIndex = i;
// Make a unit cube by having the vertices (at index N)
// while leaving the normals (at index N + 1) alone
if (VERTEX_OFFSET == vertexIndex % VERTEX_FORMAT_SIZE) {
vertices[vertexIndex] *= 0.5f;
}
}
shapeData.setupVertices(_shapeVertices, vertices);
IndexVector indices{
0, 1, 2, 2, 3, 0, // front
4, 5, 6, 6, 7, 4, // right
8, 9, 10, 10, 11, 8, // top
12, 13, 14, 14, 15, 12, // left
16, 17, 18, 18, 19, 16, // bottom
20, 21, 22, 22, 23, 20 // back
};
for (auto& index : indices) {
index += (uint16_t)startingIndex;
}
IndexVector wireIndices{
0, 1, 1, 2, 2, 3, 3, 0, // front
20, 21, 21, 22, 22, 23, 23, 20, // back
0, 23, 1, 22, 2, 21, 3, 20 // sides
};
for (size_t i = 0; i < wireIndices.size(); ++i) {
indices[i] += (uint16_t)startingIndex;
}
shapeData.setupIndices(_shapeIndices, indices, wireIndices);
}
setupFlatShape(_shapes[Cube], cube(), _shapeVertices, _shapeIndices);
// Tetrahedron
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
{
ShapeData& shapeData = _shapes[Tetrahedron];
size_t vertexCount = 4;
VertexVector vertices;
{
VertexVector originalVertices = tetrahedronVertices();
vertexCount = originalVertices.size();
vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER);
for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) {
auto triangleStartIndex = i;
vec3 faceNormal;
for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
auto triangleVertexIndex = j;
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
faceNormal += originalVertices[vertexIndex];
}
faceNormal = glm::normalize(faceNormal);
for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
auto triangleVertexIndex = j;
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
vertices.push_back(originalVertices[vertexIndex]);
vertices.push_back(faceNormal);
}
}
}
shapeData.setupVertices(_shapeVertices, vertices);
IndexVector indices;
for (size_t i = 0; i < vertexCount; i += VERTICES_PER_TRIANGLE) {
auto triangleStartIndex = i;
for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
auto triangleVertexIndex = j;
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
indices.push_back((uint16_t)(vertexIndex + startingIndex));
}
}
IndexVector wireIndices{
0, 1, 1, 2, 2, 0,
0, 3, 1, 3, 2, 3,
};
for (size_t i = 0; i < wireIndices.size(); ++i) {
wireIndices[i] += (uint16_t)startingIndex;
}
shapeData.setupIndices(_shapeIndices, indices, wireIndices);
}
setupFlatShape(_shapes[Tetrahedron], tetrahedron(), _shapeVertices, _shapeIndices);
// Icosahedron
setupFlatShape(_shapes[Icosahedron], icosahedron(), _shapeVertices, _shapeIndices);
// Octahedron
setupFlatShape(_shapes[Octahedron], octahedron(), _shapeVertices, _shapeIndices);
// Dodecahedron
setupFlatShape(_shapes[Dodecahedron], dodecahedron(), _shapeVertices, _shapeIndices);
// Sphere
// FIXME this uses way more vertices than required. Should find a way to calculate the indices
// using shared vertices for better vertex caching
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
{
ShapeData& shapeData = _shapes[Sphere];
VertexVector vertices;
IndexVector indices;
{
VertexVector originalVertices = tesselate(icosahedronVertices(), ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT);
vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER);
for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) {
auto triangleStartIndex = i;
for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
auto triangleVertexIndex = j;
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
const auto& vertex = originalVertices[i + j];
// Spheres use the same values for vertices and normals
vertices.push_back(vertex);
vertices.push_back(vertex);
indices.push_back((uint16_t)(vertexIndex + startingIndex));
}
}
}
shapeData.setupVertices(_shapeVertices, vertices);
// FIXME don't use solid indices for wire drawing.
shapeData.setupIndices(_shapeIndices, indices, indices);
}
// Icosahedron
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
{
ShapeData& shapeData = _shapes[Icosahedron];
VertexVector vertices;
IndexVector indices;
{
const VertexVector& originalVertices = icosahedronVertices();
vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER);
for (size_t i = 0; i < originalVertices.size(); i += 3) {
auto triangleStartIndex = i;
vec3 faceNormal;
for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
auto triangleVertexIndex = j;
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
faceNormal += originalVertices[vertexIndex];
}
faceNormal = glm::normalize(faceNormal);
for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
auto triangleVertexIndex = j;
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
vertices.push_back(originalVertices[vertexIndex]);
vertices.push_back(faceNormal);
indices.push_back((uint16_t)(vertexIndex + startingIndex));
}
}
}
shapeData.setupVertices(_shapeVertices, vertices);
// FIXME don't use solid indices for wire drawing.
shapeData.setupIndices(_shapeIndices, indices, indices);
}
Solid<3> sphere = icosahedron();
sphere = tesselate(sphere, ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT);
sphere.fitDimension(1.0f);
setupSmoothShape(_shapes[Sphere], sphere, _shapeVertices, _shapeIndices);
// Line
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
{
Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE);
ShapeData& shapeData = _shapes[Line];
shapeData.setupVertices(_shapeVertices, VertexVector{
vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0),
@ -460,9 +482,8 @@ void GeometryCache::buildShapes() {
});
IndexVector wireIndices;
// Only two indices
wireIndices.push_back(0 + (uint16_t)startingIndex);
wireIndices.push_back(1 + (uint16_t)startingIndex);
wireIndices.push_back(0 + baseVertex);
wireIndices.push_back(1 + baseVertex);
shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices);
}

View file

@ -121,8 +121,8 @@ inline uint qHash(const Vec4PairVec4Pair& v, uint seed) {
seed);
}
using IndexVector = std::vector<uint32_t>;
using VertexVector = std::vector<glm::vec3>;
using IndexVector = std::vector<uint16_t>;
/// Stores cached geometry.
class GeometryCache : public Dependency {
@ -137,7 +137,7 @@ public:
Cube,
Sphere,
Tetrahedron,
Octahetron,
Octahedron,
Dodecahedron,
Icosahedron,
Torus,

View file

@ -1368,10 +1368,12 @@
<div class="shape-group shape-section property dropdown">
<label for="property-shape">Shape</label>
<select name="SelectShape" id="property-shape">
<option value="Cube">Cube</option>
<option value="Cube">Box</option>
<option value="Sphere">Sphere</option>
<option value="Icosahedron">Icosahedron</option>
<option value="Tetrahedron">Tetrahedron</option>
<option value="Octahedron">Octahedron</option>
<option value="Icosahedron">Icosahedron</option>
<option value="Dodecahedron">Dodecahedron</option>
</select>
</div>
<div class="shape-group shape-section property text">

View file

@ -4,4 +4,6 @@ AUTOSCRIBE_SHADER_LIB(gpu model render-utils)
setup_hifi_project(Quick Gui OpenGL Script Widgets)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils )
package_libraries_for_deployment()
package_libraries_for_deployment()
target_nsight()

View file

@ -0,0 +1,20 @@
//
// Created by Bradley Austin Davis on 2016/05/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TestHelpers.h"
gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) {
auto vs = gpu::Shader::createVertex(vertexShaderSrc);
auto fs = gpu::Shader::createPixel(fragmentShaderSrc);
auto shader = gpu::Shader::createProgram(vs, fs);
if (!gpu::Shader::makeProgram(*shader, bindings)) {
printf("Could not compile shader\n");
exit(-1);
}
return shader;
}

View file

@ -0,0 +1,33 @@
//
// Created by Bradley Austin Davis on 2016/05/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#include <vector>
#include <GLMHelpers.h>
#include <Transform.h>
#include <GeometryCache.h>
#include <NumericalConstants.h>
#include <gpu/Resource.h>
#include <gpu/Forward.h>
#include <gpu/Shader.h>
#include <gpu/Stream.h>
class GpuTestBase {
public:
virtual ~GpuTestBase() {}
virtual bool isReady() const { return true; }
virtual size_t getTestCount() const { return 1; }
virtual void renderTest(size_t test, RenderArgs* args) = 0;
};
uint32_t toCompactColor(const glm::vec4& color);
gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings);

View file

@ -0,0 +1,84 @@
//
// Created by Bradley Austin Davis on 2016/05/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TestInstancedShapes.h"
gpu::Stream::FormatPointer& getInstancedSolidStreamFormat();
static const size_t TYPE_COUNT = 4;
static const size_t ITEM_COUNT = 50;
static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT;
static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT;
static GeometryCache::Shape SHAPE[TYPE_COUNT] = {
GeometryCache::Icosahedron,
GeometryCache::Cube,
GeometryCache::Sphere,
GeometryCache::Tetrahedron,
};
const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ };
const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ };
const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA };
TestInstancedShapes::TestInstancedShapes() {
auto geometryCache = DependencyManager::get<GeometryCache>();
colorBuffer = std::make_shared<gpu::Buffer>();
static const float ITEM_RADIUS = 20;
static const vec3 ITEM_TRANSLATION { 0, 0, -ITEM_RADIUS };
for (size_t i = 0; i < TYPE_COUNT; ++i) {
GeometryCache::Shape shape = SHAPE[i];
GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape];
//indirectCommand._count
float startingInterval = ITEM_INTERVAL * i;
std::vector<mat4> typeTransforms;
for (size_t j = 0; j < ITEM_COUNT; ++j) {
float theta = j * SHAPE_INTERVAL + startingInterval;
auto transform = glm::rotate(mat4(), theta, Vectors::UP);
transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X);
transform = glm::translate(transform, ITEM_TRANSLATION);
transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f));
typeTransforms.push_back(transform);
auto color = vec4 { randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 };
color /= 255.0f;
colors.push_back(color);
colorBuffer->append(toCompactColor(color));
}
transforms.push_back(typeTransforms);
}
}
void TestInstancedShapes::renderTest(size_t testId, RenderArgs* args) {
gpu::Batch& batch = *(args->_batch);
auto geometryCache = DependencyManager::get<GeometryCache>();
geometryCache->bindSimpleProgram(batch);
batch.setInputFormat(getInstancedSolidStreamFormat());
for (size_t i = 0; i < TYPE_COUNT; ++i) {
GeometryCache::Shape shape = SHAPE[i];
GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape];
std::string namedCall = __FUNCTION__ + std::to_string(i);
//batch.addInstanceModelTransforms(transforms[i]);
for (size_t j = 0; j < ITEM_COUNT; ++j) {
batch.setModelTransform(transforms[i][j]);
batch.setupNamedCalls(namedCall, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData&) {
batch.setInputBuffer(gpu::Stream::COLOR, gpu::BufferView(colorBuffer, i * ITEM_COUNT * 4, colorBuffer->getSize(), COLOR_ELEMENT));
shapeData.drawInstances(batch, ITEM_COUNT);
});
}
//for (size_t j = 0; j < ITEM_COUNT; ++j) {
// batch.setModelTransform(transforms[j + i * ITEM_COUNT]);
// shapeData.draw(batch);
//}
}
}

View file

@ -0,0 +1,23 @@
//
// Created by Bradley Austin Davis on 2016/05/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#include "TestHelpers.h"
class TestInstancedShapes : public GpuTestBase {
std::vector<std::vector<mat4>> transforms;
std::vector<vec4> colors;
gpu::BufferPointer colorBuffer;
gpu::BufferView instanceXfmView;
public:
TestInstancedShapes();
void renderTest(size_t testId, RenderArgs* args) override;
};

View file

@ -0,0 +1,48 @@
//
// Created by Bradley Austin Davis on 2016/05/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TestShapes.h"
static const size_t TYPE_COUNT = 6;
static GeometryCache::Shape SHAPE[TYPE_COUNT] = {
GeometryCache::Cube,
GeometryCache::Tetrahedron,
GeometryCache::Octahedron,
GeometryCache::Dodecahedron,
GeometryCache::Icosahedron,
GeometryCache::Sphere,
};
void TestShapes::renderTest(size_t testId, RenderArgs* args) {
gpu::Batch& batch = *(args->_batch);
auto geometryCache = DependencyManager::get<GeometryCache>();
geometryCache->bindSimpleProgram(batch);
// Render unlit cube + sphere
static auto startSecs = secTimestampNow();
float seconds = secTimestampNow() - startSecs;
seconds /= 4.0f;
batch.setModelTransform(Transform());
batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f);
bool wire = (seconds - floorf(seconds) > 0.5f);
int shapeIndex = ((int)seconds) % TYPE_COUNT;
if (wire) {
geometryCache->renderWireShape(batch, SHAPE[shapeIndex]);
} else {
geometryCache->renderShape(batch, SHAPE[shapeIndex]);
}
batch.setModelTransform(Transform().setScale(1.01f));
batch._glColor4f(1, 1, 1, 1);
geometryCache->renderWireCube(batch);
}

View file

@ -0,0 +1,22 @@
//
// Created by Bradley Austin Davis on 2016/05/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#include "TestHelpers.h"
class TestShapes : public GpuTestBase {
std::vector<std::vector<mat4>> transforms;
std::vector<vec4> colors;
gpu::BufferPointer colorBuffer;
gpu::BufferView instanceXfmView;
public:
void renderTest(size_t testId, RenderArgs* args) override;
};

View file

@ -0,0 +1,180 @@
//
// Created by Bradley Austin Davis on 2016/05/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TestWindow.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <QtCore/QTimer>
#include <QtGui/QResizeEvent>
#include <gl/GLHelpers.h>
#include <gl/QOpenGLDebugLoggerWrapper.h>
#include <gpu/gl/GLBackend.h>
#include <GeometryCache.h>
#include <DeferredLightingEffect.h>
#include <FramebufferCache.h>
#include <TextureCache.h>
#ifdef DEFERRED_LIGHTING
extern void initDeferredPipelines(render::ShapePlumber& plumber);
extern void initStencilPipeline(gpu::PipelinePointer& pipeline);
#endif
TestWindow::TestWindow() {
setSurfaceType(QSurface::OpenGLSurface);
auto timer = new QTimer(this);
timer->setInterval(5);
connect(timer, &QTimer::timeout, [&] { draw(); });
timer->start();
connect(qApp, &QCoreApplication::aboutToQuit, [this, timer] {
timer->stop();
_aboutToQuit = true;
});
#ifdef DEFERRED_LIGHTING
_light->setType(model::Light::SUN);
_light->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset::OLD_TOWN_SQUARE);
_light->setIntensity(1.0f);
_light->setAmbientIntensity(0.5f);
_light->setColor(vec3(1.0f));
_light->setPosition(vec3(1, 1, 1));
_renderContext->args = _renderArgs;
#endif
QSurfaceFormat format = getDefaultOpenGLSurfaceFormat();
format.setOption(QSurfaceFormat::DebugContext);
//format.setSwapInterval(0);
setFormat(format);
_glContext.setFormat(format);
_glContext.create();
_glContext.makeCurrent(this);
show();
}
void TestWindow::initGl() {
_glContext.makeCurrent(this);
gpu::Context::init<gpu::gl::GLBackend>();
_renderArgs->_context = std::make_shared<gpu::Context>();
_glContext.makeCurrent(this);
DependencyManager::set<GeometryCache>();
DependencyManager::set<TextureCache>();
DependencyManager::set<FramebufferCache>();
DependencyManager::set<DeferredLightingEffect>();
resize(QSize(800, 600));
setupDebugLogger(this);
#ifdef DEFERRED_LIGHTING
auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();
deferredLightingEffect->init();
deferredLightingEffect->setGlobalLight(_light);
initDeferredPipelines(*_shapePlumber);
initStencilPipeline(_opaquePipeline);
#endif
}
void TestWindow::resizeWindow(const QSize& size) {
_size = size;
_renderArgs->_viewport = ivec4(0, 0, _size.width(), _size.height());
auto fboCache = DependencyManager::get<FramebufferCache>();
if (fboCache) {
fboCache->setFrameBufferSize(_size);
}
}
void TestWindow::beginFrame() {
_renderArgs->_context->syncCache();
#ifdef DEFERRED_LIGHTING
auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();
deferredLightingEffect->prepare(_renderArgs);
#else
gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) {
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f });
batch.clearDepthFramebuffer(1e4);
batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() });
});
#endif
gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) {
batch.setViewportTransform(_renderArgs->_viewport);
batch.setStateScissorRect(_renderArgs->_viewport);
batch.setProjectionTransform(_projectionMatrix);
});
}
void TestWindow::endFrame() {
#ifdef DEFERRED_LIGHTING
RenderArgs* args = _renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
auto deferredFboColorDepthStencil = DependencyManager::get<FramebufferCache>()->getDeferredFramebufferDepthColor();
batch.setViewportTransform(args->_viewport);
batch.setStateScissorRect(args->_viewport);
batch.setFramebuffer(deferredFboColorDepthStencil);
batch.setPipeline(_opaquePipeline);
batch.draw(gpu::TRIANGLE_STRIP, 4);
batch.setResourceTexture(0, nullptr);
});
auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();
deferredLightingEffect->render(_renderContext);
gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) {
PROFILE_RANGE_BATCH(batch, "blit");
// Blit to screen
auto framebufferCache = DependencyManager::get<FramebufferCache>();
auto framebuffer = framebufferCache->getLightingFramebuffer();
batch.blit(framebuffer, _renderArgs->_viewport, nullptr, _renderArgs->_viewport);
});
#endif
gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) {
batch.resetStages();
});
_glContext.swapBuffers(this);
}
void TestWindow::draw() {
if (_aboutToQuit) {
return;
}
// Attempting to draw before we're visible and have a valid size will
// produce GL errors.
if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) {
return;
}
if (!_glContext.makeCurrent(this)) {
return;
}
static std::once_flag once;
std::call_once(once, [&] { initGl(); });
beginFrame();
renderFrame();
endFrame();
}
void TestWindow::resizeEvent(QResizeEvent* ev) {
resizeWindow(ev->size());
float fov_degrees = 60.0f;
float aspect_ratio = (float)_size.width() / _size.height();
float near_clip = 0.1f;
float far_clip = 1000.0f;
_projectionMatrix = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip);
}

View file

@ -0,0 +1,53 @@
//
// Created by Bradley Austin Davis on 2016/05/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#include <QtGui/QWindow>
#include <QtCore/QTime>
#include <GLMHelpers.h>
#include <gl/QOpenGLContextWrapper.h>
#include <gpu/Forward.h>
#include <DeferredLightingEffect.h>
#include <render/ShapePipeline.h>
#include <render/Context.h>
#define DEFERRED_LIGHTING
class TestWindow : public QWindow {
protected:
QOpenGLContextWrapper _glContext;
QSize _size;
glm::mat4 _projectionMatrix;
bool _aboutToQuit { false };
#ifdef DEFERRED_LIGHTING
// Prepare the ShapePipelines
render::ShapePlumberPointer _shapePlumber { std::make_shared<render::ShapePlumber>() };
render::RenderContextPointer _renderContext { std::make_shared<render::RenderContext>() };
gpu::PipelinePointer _opaquePipeline;
model::LightPointer _light { std::make_shared<model::Light>() };
#endif
RenderArgs* _renderArgs { new RenderArgs() };
TestWindow();
virtual void initGl();
virtual void renderFrame() = 0;
private:
void resizeWindow(const QSize& size);
void beginFrame();
void endFrame();
void draw();
void resizeEvent(QResizeEvent* ev) override;
};

View file

@ -42,238 +42,64 @@
#include <GeometryCache.h>
#include <DeferredLightingEffect.h>
#include <FramebufferCache.h>
#include <TextureCache.h>
#include <PerfStat.h>
#include <PathUtils.h>
#include <RenderArgs.h>
#include <ViewFrustum.h>
#include "unlit_frag.h"
#include "unlit_vert.h"
#include <gpu/Pipeline.h>
#include <gpu/Context.h>
class RateCounter {
std::vector<float> times;
QElapsedTimer timer;
public:
RateCounter() {
timer.start();
}
#include <render/Engine.h>
#include <render/Scene.h>
#include <render/CullTask.h>
#include <render/SortTask.h>
#include <render/DrawTask.h>
#include <render/DrawStatus.h>
#include <render/DrawSceneOctree.h>
#include <render/CullTask.h>
void reset() {
times.clear();
}
#include "TestWindow.h"
#include "TestInstancedShapes.h"
#include "TestShapes.h"
unsigned int count() const {
return (unsigned int)times.size() - 1;
}
float elapsed() const {
if (times.size() < 1) {
return 0.0f;
}
float elapsed = *times.rbegin() - *times.begin();
return elapsed;
}
void increment() {
times.push_back(timer.elapsed() / 1000.0f);
}
float rate() const {
if (elapsed() == 0.0f) {
return NAN;
}
return (float) count() / elapsed();
}
};
uint32_t toCompactColor(const glm::vec4& color);
using namespace render;
const char* VERTEX_SHADER = R"SHADER(
layout(location = 0) in vec4 inPosition;
layout(location = 3) in vec2 inTexCoord0;
struct TransformObject {
mat4 _model;
mat4 _modelInverse;
};
layout(location=15) in ivec2 _drawCallInfo;
uniform samplerBuffer transformObjectBuffer;
TransformObject getTransformObject() {
int offset = 8 * _drawCallInfo.x;
TransformObject object;
object._model[0] = texelFetch(transformObjectBuffer, offset);
object._model[1] = texelFetch(transformObjectBuffer, offset + 1);
object._model[2] = texelFetch(transformObjectBuffer, offset + 2);
object._model[3] = texelFetch(transformObjectBuffer, offset + 3);
object._modelInverse[0] = texelFetch(transformObjectBuffer, offset + 4);
object._modelInverse[1] = texelFetch(transformObjectBuffer, offset + 5);
object._modelInverse[2] = texelFetch(transformObjectBuffer, offset + 6);
object._modelInverse[3] = texelFetch(transformObjectBuffer, offset + 7);
return object;
}
struct TransformCamera {
mat4 _view;
mat4 _viewInverse;
mat4 _projectionViewUntranslated;
mat4 _projection;
mat4 _projectionInverse;
vec4 _viewport;
};
layout(std140) uniform transformCameraBuffer {
TransformCamera _camera;
};
TransformCamera getTransformCamera() {
return _camera;
}
// the interpolated normal
out vec2 _texCoord0;
void main(void) {
_texCoord0 = inTexCoord0.st;
// standard transform
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
{ // transformModelToClipPos
vec4 eyeWAPos;
{ // _transformModelToEyeWorldAlignedPos
highp mat4 _mv = obj._model;
_mv[3].xyz -= cam._viewInverse[3].xyz;
highp vec4 _eyeWApos = (_mv * inPosition);
eyeWAPos = _eyeWApos;
}
gl_Position = cam._projectionViewUntranslated * eyeWAPos;
}
})SHADER";
const char* FRAGMENT_SHADER = R"SHADER(
uniform sampler2D originalTexture;
in vec2 _texCoord0;
layout(location = 0) out vec4 _fragColor0;
void main(void) {
//_fragColor0 = vec4(_texCoord0, 0.0, 1.0);
_fragColor0 = texture(originalTexture, _texCoord0);
}
)SHADER";
using TestBuilder = std::function<GpuTestBase*()>;
using TestBuilders = std::list<TestBuilder>;
gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) {
auto vs = gpu::Shader::createVertex(vertexShaderSrc);
auto fs = gpu::Shader::createPixel(fragmentShaderSrc);
auto shader = gpu::Shader::createProgram(vs, fs);
if (!gpu::Shader::makeProgram(*shader, bindings)) {
printf("Could not compile shader\n");
exit(-1);
}
return shader;
}
#define INTERACTIVE
float getSeconds(quint64 start = 0) {
auto usecs = usecTimestampNow() - start;
auto msecs = usecs / USECS_PER_MSEC;
float seconds = (float)msecs / MSECS_PER_SECOND;
return seconds;
}
static const size_t TYPE_COUNT = 4;
static GeometryCache::Shape SHAPE[TYPE_COUNT] = {
GeometryCache::Icosahedron,
GeometryCache::Cube,
GeometryCache::Sphere,
GeometryCache::Tetrahedron,
//GeometryCache::Line,
};
gpu::Stream::FormatPointer& getInstancedSolidStreamFormat();
// Creates an OpenGL window that renders a simple unlit scene using the gpu library and GeometryCache
// Should eventually get refactored into something that supports multiple gpu backends.
class QTestWindow : public QWindow {
Q_OBJECT
QOpenGLContextWrapper _qGlContext;
QSize _size;
gpu::ContextPointer _context;
gpu::PipelinePointer _pipeline;
glm::mat4 _projectionMatrix;
RateCounter fps;
QTime _time;
class MyTestWindow : public TestWindow {
using Parent = TestWindow;
TestBuilders _testBuilders;
GpuTestBase* _currentTest { nullptr };
size_t _currentTestId { 0 };
size_t _currentMaxTests { 0 };
glm::mat4 _camera;
QTime _time;
protected:
void renderText();
private:
void resizeWindow(const QSize& size) {
_size = size;
}
public:
QTestWindow() {
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format;
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
setGLFormatVersion(format);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
format.setOption(QSurfaceFormat::DebugContext);
//format.setSwapInterval(0);
setFormat(format);
_qGlContext.setFormat(format);
_qGlContext.create();
show();
makeCurrent();
setupDebugLogger(this);
gpu::Context::init<gpu::gl::GLBackend>();
_context = std::make_shared<gpu::Context>();
makeCurrent();
auto shader = makeShader(unlit_vert, unlit_frag, gpu::Shader::BindingSet{});
auto state = std::make_shared<gpu::State>();
state->setMultisampleEnable(true);
state->setDepthTest(gpu::State::DepthTest { true });
_pipeline = gpu::Pipeline::create(shader, state);
// Clear screen
gpu::Batch batch;
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, 0.0, 0.5, 1.0 });
_context->render(batch);
DependencyManager::set<GeometryCache>();
DependencyManager::set<TextureCache>();
DependencyManager::set<DeferredLightingEffect>();
resize(QSize(800, 600));
void initGl() override {
Parent::initGl();
#ifdef INTERACTIVE
_time.start();
}
virtual ~QTestWindow() {
#endif
updateCamera();
_testBuilders = TestBuilders({
//[this] { return new TestFbx(_shapePlumber); },
[] { return new TestShapes(); },
});
}
void updateCamera() {
float t = _time.elapsed() * 1e-4f;
float t = 0;
#ifdef INTERACTIVE
t = _time.elapsed() * 1e-3f;
#endif
glm::vec3 unitscale { 1.0f };
glm::vec3 up { 0.0f, 1.0f, 0.0f };
@ -283,263 +109,64 @@ public:
static const vec3 camera_focus(0);
static const vec3 camera_up(0, 1, 0);
_camera = glm::inverse(glm::lookAt(camera_position, camera_focus, up));
ViewFrustum frustum;
frustum.setPosition(camera_position);
frustum.setOrientation(glm::quat_cast(_camera));
frustum.setProjection(_projectionMatrix);
_renderArgs->setViewFrustum(frustum);
}
void renderFrame() override {
updateCamera();
void drawFloorGrid(gpu::Batch& batch) {
auto geometryCache = DependencyManager::get<GeometryCache>();
// Render grid on xz plane (not the optimal way to do things, but w/e)
// Note: GeometryCache::renderGrid will *not* work, as it is apparenly unaffected by batch rotations and renders xy only
static const std::string GRID_INSTANCE = "Grid";
static auto compactColor1 = toCompactColor(vec4 { 0.35f, 0.25f, 0.15f, 1.0f });
static auto compactColor2 = toCompactColor(vec4 { 0.15f, 0.25f, 0.35f, 1.0f });
static std::vector<glm::mat4> transforms;
static gpu::BufferPointer colorBuffer;
if (!transforms.empty()) {
transforms.reserve(200);
colorBuffer = std::make_shared<gpu::Buffer>();
for (int i = 0; i < 100; ++i) {
{
glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i));
transform = glm::scale(transform, vec3(100, 1, 1));
transforms.push_back(transform);
colorBuffer->append(compactColor1);
}
while ((!_currentTest || (_currentTestId >= _currentMaxTests)) && !_testBuilders.empty()) {
if (_currentTest) {
delete _currentTest;
_currentTest = nullptr;
}
{
glm::mat4 transform = glm::mat4_cast(quat(vec3(0, PI / 2.0f, 0)));
transform = glm::translate(transform, vec3(0, -1, -50 + i));
transform = glm::scale(transform, vec3(100, 1, 1));
transforms.push_back(transform);
colorBuffer->append(compactColor2);
}
_currentTest = _testBuilders.front()();
_testBuilders.pop_front();
if (_currentTest) {
_currentMaxTests = _currentTest->getTestCount();
_currentTestId = 0;
}
}
auto pipeline = geometryCache->getSimplePipeline();
for (auto& transform : transforms) {
batch.setModelTransform(transform);
batch.setupNamedCalls(GRID_INSTANCE, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
batch.setViewTransform(_camera);
batch.setPipeline(_pipeline);
geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count(), colorBuffer);
});
}
}
void drawSimpleShapes(gpu::Batch& batch) {
auto geometryCache = DependencyManager::get<GeometryCache>();
static const size_t ITEM_COUNT = 1000;
static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT;
static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT;
static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ };
static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ };
static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA };
static std::vector<Transform> transforms;
static std::vector<vec4> colors;
static gpu::BufferPointer colorBuffer;
static gpu::BufferView colorView;
static gpu::BufferView instanceXfmView;
if (!colorBuffer) {
colorBuffer = std::make_shared<gpu::Buffer>();
static const float ITEM_RADIUS = 20;
static const vec3 ITEM_TRANSLATION { 0, 0, -ITEM_RADIUS };
for (size_t i = 0; i < TYPE_COUNT; ++i) {
GeometryCache::Shape shape = SHAPE[i];
GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape];
//indirectCommand._count
float startingInterval = ITEM_INTERVAL * i;
for (size_t j = 0; j < ITEM_COUNT; ++j) {
float theta = j * SHAPE_INTERVAL + startingInterval;
auto transform = glm::rotate(mat4(), theta, Vectors::UP);
transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X);
transform = glm::translate(transform, ITEM_TRANSLATION);
transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f));
transforms.push_back(transform);
auto color = vec4 { randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 };
color /= 255.0f;
colors.push_back(color);
colorBuffer->append(toCompactColor(color));
}
}
colorView = gpu::BufferView(colorBuffer, COLOR_ELEMENT);
}
batch.setViewTransform(_camera);
batch.setPipeline(_pipeline);
batch.setInputFormat(getInstancedSolidStreamFormat());
for (size_t i = 0; i < TYPE_COUNT; ++i) {
GeometryCache::Shape shape = SHAPE[i];
GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape];
batch.setInputBuffer(gpu::Stream::COLOR, colorView);
for (size_t j = 0; j < ITEM_COUNT; ++j) {
batch.setModelTransform(transforms[j]);
shapeData.draw(batch);
}
}
}
void drawCenterShape(gpu::Batch& batch) {
// Render unlit cube + sphere
static auto startUsecs = usecTimestampNow();
float seconds = getSeconds(startUsecs);
seconds /= 4.0f;
batch.setModelTransform(Transform());
batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f);
bool wire = (seconds - floorf(seconds) > 0.5f);
auto geometryCache = DependencyManager::get<GeometryCache>();
int shapeIndex = ((int)seconds) % TYPE_COUNT;
if (wire) {
geometryCache->renderWireShape(batch, SHAPE[shapeIndex]);
} else {
geometryCache->renderShape(batch, SHAPE[shapeIndex]);
}
batch.setModelTransform(Transform().setScale(2.05f));
batch._glColor4f(1, 1, 1, 1);
geometryCache->renderWireCube(batch);
}
void drawTerrain(gpu::Batch& batch) {
auto geometryCache = DependencyManager::get<GeometryCache>();
static std::once_flag once;
static gpu::BufferPointer vertexBuffer { std::make_shared<gpu::Buffer>() };
static gpu::BufferPointer indexBuffer { std::make_shared<gpu::Buffer>() };
static gpu::BufferView positionView;
static gpu::BufferView textureView;
static gpu::Stream::FormatPointer vertexFormat { std::make_shared<gpu::Stream::Format>() };
static gpu::TexturePointer texture;
static gpu::PipelinePointer pipeline;
std::call_once(once, [&] {
static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec4) * 2; // position, normals, textures
static const uint SHAPE_TEXTURES_OFFSET = sizeof(glm::vec4);
static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ };
static const gpu::Element TEXTURE_ELEMENT { gpu::VEC2, gpu::FLOAT, gpu::UV };
std::vector<vec4> vertices;
const int MINX = -1000;
const int MAXX = 1000;
// top
vertices.push_back(vec4(MAXX, 0, MAXX, 1));
vertices.push_back(vec4(MAXX, MAXX, 0, 0));
vertices.push_back(vec4(MAXX, 0, MINX, 1));
vertices.push_back(vec4(MAXX, 0, 0, 0));
vertices.push_back(vec4(MINX, 0, MINX, 1));
vertices.push_back(vec4(0, 0, 0, 0));
vertices.push_back(vec4(MINX, 0, MAXX, 1));
vertices.push_back(vec4(0, MAXX, 0, 0));
vertexBuffer->append(vertices);
indexBuffer->append(std::vector<uint16_t>({ 0, 1, 2, 2, 3, 0 }));
positionView = gpu::BufferView(vertexBuffer, 0, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, POSITION_ELEMENT);
textureView = gpu::BufferView(vertexBuffer, SHAPE_TEXTURES_OFFSET, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, TEXTURE_ELEMENT);
texture = DependencyManager::get<TextureCache>()->getImageTexture("C:/Users/bdavis/Git/openvr/samples/bin/cube_texture.png");
// texture = DependencyManager::get<TextureCache>()->getImageTexture("H:/test.png");
//texture = DependencyManager::get<TextureCache>()->getImageTexture("H:/crate_blue.fbm/lambert8SG_Normal_OpenGL.png");
auto shader = makeShader(VERTEX_SHADER, FRAGMENT_SHADER, gpu::Shader::BindingSet {});
auto state = std::make_shared<gpu::State>();
state->setMultisampleEnable(false);
state->setDepthTest(gpu::State::DepthTest { true });
pipeline = gpu::Pipeline::create(shader, state);
vertexFormat->setAttribute(gpu::Stream::POSITION);
vertexFormat->setAttribute(gpu::Stream::TEXCOORD);
});
static auto start = usecTimestampNow();
auto now = usecTimestampNow();
if ((now - start) > USECS_PER_SECOND * 1) {
start = now;
texture->incremementMinMip();
}
batch.setPipeline(pipeline);
batch.setInputBuffer(gpu::Stream::POSITION, positionView);
batch.setInputBuffer(gpu::Stream::TEXCOORD, textureView);
batch.setIndexBuffer(gpu::UINT16, indexBuffer, 0);
batch.setInputFormat(vertexFormat);
batch.setResourceTexture(0, texture);
batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.1, 0)));
batch.drawIndexed(gpu::TRIANGLES, 6, 0);
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getBlueTexture());
batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.2, 0)));
batch.drawIndexed(gpu::TRIANGLES, 6, 0);
}
void draw() {
// Attempting to draw before we're visible and have a valid size will
// produce GL errors.
if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) {
if (!_currentTest && _testBuilders.empty()) {
qApp->quit();
return;
}
updateCamera();
makeCurrent();
gpu::Batch batch;
batch.resetStages();
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f });
batch.clearDepthFramebuffer(1e4);
batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() });
batch.setProjectionTransform(_projectionMatrix);
batch.setViewTransform(_camera);
batch.setPipeline(_pipeline);
batch.setModelTransform(Transform());
//drawFloorGrid(batch);
//drawSimpleShapes(batch);
//drawCenterShape(batch);
drawTerrain(batch);
_context->render(batch);
_qGlContext.swapBuffers(this);
fps.increment();
if (fps.elapsed() >= 0.5f) {
qDebug() << "FPS: " << fps.rate();
fps.reset();
// Tests might need to wait for resources to download
if (!_currentTest->isReady()) {
return;
}
}
void makeCurrent() {
_qGlContext.makeCurrent(this);
}
protected:
void resizeEvent(QResizeEvent* ev) override {
resizeWindow(ev->size());
float fov_degrees = 60.0f;
float aspect_ratio = (float)_size.width() / _size.height();
float near_clip = 0.1f;
float far_clip = 1000.0f;
_projectionMatrix = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip);
}
gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) {
batch.setViewTransform(_camera);
_renderArgs->_batch = &batch;
_currentTest->renderTest(_currentTestId, _renderArgs);
_renderArgs->_batch = nullptr;
});
#ifdef INTERACTIVE
#else
// TODO Capture the current rendered framebuffer and save
// Increment the test ID
++_currentTestId;
#endif
}
};
int main(int argc, char** argv) {
QGuiApplication app(argc, argv);
QTestWindow window;
auto timer = new QTimer(&app);
timer->setInterval(0);
app.connect(timer, &QTimer::timeout, &app, [&] {
window.draw();
});
timer->start();
MyTestWindow window;
app.exec();
return 0;
}
#include "main.moc"