diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 7d30b7a47c..c93ae252e3 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -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 diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index a24c7e1df5..2527dedab2 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -27,7 +27,7 @@ namespace entity { "Cube", "Sphere", "Tetrahedron", - "Octahetron", + "Octahedron", "Dodecahedron", "Icosahedron", "Torus", diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index f3cb2abd66..2ae4ae2ca1 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -19,7 +19,7 @@ namespace entity { Cube, Sphere, Tetrahedron, - Octahetron, + Octahedron, Dodecahedron, Icosahedron, Torus, diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 6852d17882..02aca4216e 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -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; + +template +using Face = std::array; + +template +using FaceVector = std::vector>; + +template +struct Solid { + VertexVector vertices; + FaceVector faces; + + Solid& 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 +static size_t triangulatedFaceTriangleCount() { + return N - 2; +} + +template +static size_t triangulatedFaceIndexCount() { + return triangulatedFaceTriangleCount() * 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 +void setupFlatShape(GeometryCache::ShapeData& shapeData, const Solid& 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(); + + vertices.reserve(N * faceCount * 2); + solidIndices.reserve(faceIndexCount * faceCount); + + for (size_t f = 0; f < faceCount; ++f) { + const Face& 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 +void setupSmoothShape(GeometryCache::ShapeData& shapeData, const Solid& 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(); + + solidIndices.reserve(faceIndexCount * faceCount); + + for (size_t f = 0; f < faceCount; ++f) { + const Face& 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(); auto indexBuffer = std::make_shared(); - 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); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 7fa543abe2..a2f79de029 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -121,8 +121,8 @@ inline uint qHash(const Vec4PairVec4Pair& v, uint seed) { seed); } +using IndexVector = std::vector; using VertexVector = std::vector; -using IndexVector = std::vector; /// Stores cached geometry. class GeometryCache : public Dependency { @@ -137,7 +137,7 @@ public: Cube, Sphere, Tetrahedron, - Octahetron, + Octahedron, Dodecahedron, Icosahedron, Torus, diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 4a3b5a14a4..c611fc5b38 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -1368,10 +1368,12 @@
diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 4fc6143ff5..21ae9c5a99 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -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() \ No newline at end of file +package_libraries_for_deployment() + +target_nsight() diff --git a/tests/gpu-test/src/TestHelpers.cpp b/tests/gpu-test/src/TestHelpers.cpp new file mode 100644 index 0000000000..75586da904 --- /dev/null +++ b/tests/gpu-test/src/TestHelpers.cpp @@ -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; +} diff --git a/tests/gpu-test/src/TestHelpers.h b/tests/gpu-test/src/TestHelpers.h new file mode 100644 index 0000000000..fd8989f628 --- /dev/null +++ b/tests/gpu-test/src/TestHelpers.h @@ -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 + +#include +#include +#include +#include + +#include +#include +#include +#include + +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); + diff --git a/tests/gpu-test/src/TestInstancedShapes.cpp b/tests/gpu-test/src/TestInstancedShapes.cpp new file mode 100644 index 0000000000..6a98ee58b9 --- /dev/null +++ b/tests/gpu-test/src/TestInstancedShapes.cpp @@ -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(); + colorBuffer = std::make_shared(); + + 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 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->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); + //} + } +} + diff --git a/tests/gpu-test/src/TestInstancedShapes.h b/tests/gpu-test/src/TestInstancedShapes.h new file mode 100644 index 0000000000..b509a13e60 --- /dev/null +++ b/tests/gpu-test/src/TestInstancedShapes.h @@ -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> transforms; + std::vector colors; + gpu::BufferPointer colorBuffer; + gpu::BufferView instanceXfmView; +public: + TestInstancedShapes(); + void renderTest(size_t testId, RenderArgs* args) override; +}; + diff --git a/tests/gpu-test/src/TestShapes.cpp b/tests/gpu-test/src/TestShapes.cpp new file mode 100644 index 0000000000..253d89cf61 --- /dev/null +++ b/tests/gpu-test/src/TestShapes.cpp @@ -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->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); +} + + + diff --git a/tests/gpu-test/src/TestShapes.h b/tests/gpu-test/src/TestShapes.h new file mode 100644 index 0000000000..606d3a45f7 --- /dev/null +++ b/tests/gpu-test/src/TestShapes.h @@ -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> transforms; + std::vector colors; + gpu::BufferPointer colorBuffer; + gpu::BufferView instanceXfmView; +public: + void renderTest(size_t testId, RenderArgs* args) override; +}; + diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp new file mode 100644 index 0000000000..4fe25e989d --- /dev/null +++ b/tests/gpu-test/src/TestWindow.cpp @@ -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 +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#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(); + _renderArgs->_context = std::make_shared(); + _glContext.makeCurrent(this); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + resize(QSize(800, 600)); + + setupDebugLogger(this); +#ifdef DEFERRED_LIGHTING + auto deferredLightingEffect = DependencyManager::get(); + 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(); + if (fboCache) { + fboCache->setFrameBufferSize(_size); + } +} + +void TestWindow::beginFrame() { + _renderArgs->_context->syncCache(); + +#ifdef DEFERRED_LIGHTING + auto deferredLightingEffect = DependencyManager::get(); + 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()->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->render(_renderContext); + + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + PROFILE_RANGE_BATCH(batch, "blit"); + // Blit to screen + auto framebufferCache = DependencyManager::get(); + 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); +} diff --git a/tests/gpu-test/src/TestWindow.h b/tests/gpu-test/src/TestWindow.h new file mode 100644 index 0000000000..b7f8df48f5 --- /dev/null +++ b/tests/gpu-test/src/TestWindow.h @@ -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 +#include + +#include +#include +#include + +#include +#include +#include + +#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::RenderContextPointer _renderContext { std::make_shared() }; + gpu::PipelinePointer _opaquePipeline; + model::LightPointer _light { std::make_shared() }; +#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; +}; + diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index b80539b33a..e672fe3c86 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -42,238 +42,64 @@ #include #include +#include #include +#include +#include +#include +#include -#include "unlit_frag.h" -#include "unlit_vert.h" +#include +#include -class RateCounter { - std::vector times; - QElapsedTimer timer; -public: - RateCounter() { - timer.start(); - } +#include +#include +#include +#include +#include +#include +#include +#include - 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; +using TestBuilders = std::list; -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(); - _context = std::make_shared(); - makeCurrent(); - auto shader = makeShader(unlit_vert, unlit_frag, gpu::Shader::BindingSet{}); - auto state = std::make_shared(); - 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(); - DependencyManager::set(); - DependencyManager::set(); - - 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(); - // 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 transforms; - static gpu::BufferPointer colorBuffer; - if (!transforms.empty()) { - transforms.reserve(200); - colorBuffer = std::make_shared(); - 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(); - 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 transforms; - static std::vector colors; - static gpu::BufferPointer colorBuffer; - static gpu::BufferView colorView; - static gpu::BufferView instanceXfmView; - if (!colorBuffer) { - colorBuffer = std::make_shared(); - - 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(); - 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(); - static std::once_flag once; - static gpu::BufferPointer vertexBuffer { std::make_shared() }; - static gpu::BufferPointer indexBuffer { std::make_shared() }; - - static gpu::BufferView positionView; - static gpu::BufferView textureView; - static gpu::Stream::FormatPointer vertexFormat { std::make_shared() }; - - 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 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({ 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()->getImageTexture("C:/Users/bdavis/Git/openvr/samples/bin/cube_texture.png"); - // texture = DependencyManager::get()->getImageTexture("H:/test.png"); - //texture = DependencyManager::get()->getImageTexture("H:/crate_blue.fbm/lambert8SG_Normal_OpenGL.png"); - - auto shader = makeShader(VERTEX_SHADER, FRAGMENT_SHADER, gpu::Shader::BindingSet {}); - auto state = std::make_shared(); - 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()->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" -