blob: 8beb04c532772af8e011dd9144ef129064fd19e9 [file] [log] [blame]
//
// Copyright 2017 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This sample shows basic usage of the GL_OVR_multiview2 extension.
#include "SampleApplication.h"
#include "util/geometry_utils.h"
#include "util/shader_utils.h"
#include <iostream>
namespace
{
void FillTranslationMatrix(float xOffset, float yOffset, float zOffset, float *matrix)
{
matrix[0] = 1.0f;
matrix[1] = 0.0f;
matrix[2] = 0.0f;
matrix[3] = xOffset;
matrix[4] = 0.0f;
matrix[5] = 1.0f;
matrix[6] = 0.0f;
matrix[7] = yOffset;
matrix[8] = 0.0f;
matrix[9] = 0.0f;
matrix[10] = 1.0f;
matrix[11] = zOffset;
matrix[12] = 0.0f;
matrix[13] = 0.0f;
matrix[14] = 0.0f;
matrix[15] = 1.0f;
}
} // namespace
class MultiviewSample : public SampleApplication
{
public:
MultiviewSample(int argc, char **argv)
: SampleApplication("Multiview", argc, argv, 3, 0),
mMultiviewProgram(0),
mMultiviewPersperiveUniformLoc(-1),
mMultiviewLeftEyeCameraUniformLoc(-1),
mMultiviewRightEyeCameraUniformLoc(-1),
mMultiviewTranslationUniformLoc(-1),
mMultiviewFBO(0),
mColorTexture(0),
mDepthTexture(0),
mQuadVAO(0),
mQuadVBO(0),
mCubeVAO(0),
mCubePosVBO(0),
mCubeNormalVBO(0),
mCubeIBO(0),
mCombineProgram(0)
{}
bool initialize() override
{
// Check whether the GL_OVR_multiview(2) extension is supported. If not, abort
// initialization.
const char *allExtensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
const std::string paddedExtensions = std::string(" ") + allExtensions + std::string(" ");
if ((paddedExtensions.find(std::string(" GL_OVR_multiview2 ")) == std::string::npos) &&
(paddedExtensions.find(std::string(" GL_OVR_multiview ")) == std::string::npos))
{
std::cout << "GL_OVR_multiview(2) is not available." << std::endl;
return false;
}
// A view covers horizontally half of the screen.
int viewWidth = getWindow()->getWidth() / 2;
int viewHeight = getWindow()->getHeight();
// Create color and depth texture arrays with two layers to which we render each view.
glGenTextures(1, &mColorTexture);
glBindTexture(GL_TEXTURE_2D_ARRAY, mColorTexture);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, viewWidth, viewHeight, 2, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glGenTextures(1, &mDepthTexture);
glBindTexture(GL_TEXTURE_2D_ARRAY, mDepthTexture);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT32F, viewWidth, viewHeight, 2, 0,
GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
// Generate multiview framebuffer for layered rendering.
glGenFramebuffers(1, &mMultiviewFBO);
glBindFramebuffer(GL_FRAMEBUFFER, mMultiviewFBO);
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mColorTexture, 0, 0,
2);
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, mDepthTexture, 0, 0,
2);
GLenum drawBuffer = GL_COLOR_ATTACHMENT0;
glDrawBuffers(1, &drawBuffer);
// Check that the framebuffer is complete. Abort initialization otherwise.
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
return false;
}
// Create multiview program and query the uniform locations.
// The program has two code paths based on the gl_ViewID_OVR attribute which tells us which
// view is currently being rendered to. Based on it we decide which eye's camera matrix to
// use.
constexpr char kMultiviewVS[] =
"#version 300 es\n"
"#extension GL_OVR_multiview2 : require\n"
"layout(num_views = 2) in;\n"
"layout(location=0) in vec3 posIn;\n"
"layout(location=1) in vec3 normalIn;\n"
"uniform mat4 uPerspective;\n"
"uniform mat4 uCameraLeftEye;\n"
"uniform mat4 uCameraRightEye;\n"
"uniform mat4 uTranslation;\n"
"out vec3 oNormal;\n"
"void main()\n"
"{\n"
" vec4 p = uTranslation * vec4(posIn,1.);\n"
" if (gl_ViewID_OVR == 0u) {\n"
" p = uCameraLeftEye * p;\n"
" } else {\n"
" p = uCameraRightEye * p;\n"
" }\n"
" oNormal = normalIn;\n"
" gl_Position = uPerspective * p;\n"
"}\n";
constexpr char kMultiviewFS[] =
"#version 300 es\n"
"#extension GL_OVR_multiview2 : require\n"
"precision mediump float;\n"
"out vec4 color;\n"
"in vec3 oNormal;\n"
"void main()\n"
"{\n"
" vec3 col = 0.5 * oNormal + vec3(0.5);\n"
" color = vec4(col, 1.);\n"
"}\n";
mMultiviewProgram = CompileProgram(kMultiviewVS, kMultiviewFS);
if (!mMultiviewProgram)
{
return false;
}
mMultiviewPersperiveUniformLoc = glGetUniformLocation(mMultiviewProgram, "uPerspective");
mMultiviewLeftEyeCameraUniformLoc =
glGetUniformLocation(mMultiviewProgram, "uCameraLeftEye");
mMultiviewRightEyeCameraUniformLoc =
glGetUniformLocation(mMultiviewProgram, "uCameraRightEye");
mMultiviewTranslationUniformLoc = glGetUniformLocation(mMultiviewProgram, "uTranslation");
// Create a normal program to combine both layers of the color array texture.
constexpr char kCombineVS[] =
"#version 300 es\n"
"in vec2 vIn;\n"
"out vec2 uv;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(vIn, 0., 1.);\n"
" uv = vIn * .5 + vec2(.5);\n"
"}\n";
constexpr char kCombineFS[] =
"#version 300 es\n"
"precision mediump float;\n"
"precision mediump sampler2DArray;\n"
"uniform sampler2DArray uMultiviewTex;\n"
"in vec2 uv;\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
" float scaledX = 2.0 * uv.x;\n"
" float layer = floor(scaledX);\n"
" vec2 adjustedUV = vec2(fract(scaledX), uv.y);\n"
" vec3 texColor = texture(uMultiviewTex, vec3(adjustedUV, layer)).rgb;\n"
" color = vec4(texColor, 1.);\n"
"}\n";
mCombineProgram = CompileProgram(kCombineVS, kCombineFS);
if (!mCombineProgram)
{
return false;
}
// Generate a quad which covers the whole screen.
glGenVertexArrays(1, &mQuadVAO);
glBindVertexArray(mQuadVAO);
glGenBuffers(1, &mQuadVBO);
glBindBuffer(GL_ARRAY_BUFFER, mQuadVBO);
const float kQuadPositionData[] = {1.f, -1.f, 1.f, 1.f, -1.f, -1.f, -1.f, 1.f};
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 8, kQuadPositionData, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
// Generate a cube.
GenerateCubeGeometry(1.0f, &mCube);
glGenVertexArrays(1, &mCubeVAO);
glBindVertexArray(mCubeVAO);
glGenBuffers(1, &mCubePosVBO);
glBindBuffer(GL_ARRAY_BUFFER, mCubePosVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(angle::Vector3) * mCube.positions.size(),
mCube.positions.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(0);
glGenBuffers(1, &mCubeNormalVBO);
glBindBuffer(GL_ARRAY_BUFFER, mCubeNormalVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(angle::Vector3) * mCube.normals.size(),
mCube.normals.data(), GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(1);
glGenBuffers(1, &mCubeIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mCubeIBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mCube.indices.size(),
mCube.indices.data(), GL_STATIC_DRAW);
glBindVertexArray(0);
glEnable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
return true;
}
void destroy() override
{
glDeleteProgram(mMultiviewProgram);
glDeleteFramebuffers(1, &mMultiviewFBO);
glDeleteTextures(1, &mColorTexture);
glDeleteTextures(1, &mDepthTexture);
glDeleteVertexArrays(1, &mQuadVAO);
glDeleteBuffers(1, &mQuadVBO);
glDeleteVertexArrays(1, &mCubeVAO);
glDeleteBuffers(1, &mQuadVBO);
glDeleteBuffers(1, &mCubePosVBO);
glDeleteBuffers(1, &mCubeNormalVBO);
glDeleteBuffers(1, &mCubeIBO);
glDeleteProgram(mCombineProgram);
}
void draw() override
{
// Draw to multiview fbo.
{
// Generate the perspective projection matrix.
const int viewWidth = getWindow()->getWidth() / 2;
const int viewHeight = getWindow()->getHeight();
const float kFOV = 90.f;
const float kNear = 1.0f;
const float kFar = 100.0f;
const float kPlaneDifference = kFar - kNear;
const float kXYScale = 1.f / (tanf(kFOV / 2.0f));
const float kAspectRatio = static_cast<float>(viewWidth) / viewHeight;
float kPerspectiveProjectionMatrix[16];
kPerspectiveProjectionMatrix[0] = kXYScale / kAspectRatio;
kPerspectiveProjectionMatrix[1] = .0f;
kPerspectiveProjectionMatrix[2] = .0f;
kPerspectiveProjectionMatrix[3] = .0f;
kPerspectiveProjectionMatrix[4] = .0f;
kPerspectiveProjectionMatrix[5] = kXYScale;
kPerspectiveProjectionMatrix[6] = .0f;
kPerspectiveProjectionMatrix[7] = .0f;
kPerspectiveProjectionMatrix[8] = .0f;
kPerspectiveProjectionMatrix[9] = .0;
kPerspectiveProjectionMatrix[10] = -kFar / kPlaneDifference;
kPerspectiveProjectionMatrix[11] = -1.f;
kPerspectiveProjectionMatrix[12] = .0f;
kPerspectiveProjectionMatrix[13] = .0;
kPerspectiveProjectionMatrix[14] = -kFar * kNear / kPlaneDifference;
kPerspectiveProjectionMatrix[15] = .0;
// Generate the camera matrices for the left and right eye.
const float kXOffset = 1.5f;
const float kYOffset = 1.5f;
const float kZOffset = 5.0f;
float kLeftCameraMatrix[16];
FillTranslationMatrix(kXOffset, -kYOffset, -kZOffset, kLeftCameraMatrix);
float kRightCameraMatrix[16];
FillTranslationMatrix(-kXOffset, -kYOffset, -kZOffset, kRightCameraMatrix);
// Bind and clear the multiview framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, mMultiviewFBO);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Set the viewport to be the size as one of the views.
glViewport(0, 0, viewWidth, viewHeight);
// Bind multiview program and set matrices.
glUseProgram(mMultiviewProgram);
glUniformMatrix4fv(mMultiviewPersperiveUniformLoc, 1, GL_TRUE,
kPerspectiveProjectionMatrix);
glUniformMatrix4fv(mMultiviewLeftEyeCameraUniformLoc, 1, GL_TRUE, kLeftCameraMatrix);
glUniformMatrix4fv(mMultiviewRightEyeCameraUniformLoc, 1, GL_TRUE, kRightCameraMatrix);
glBindVertexArray(mCubeVAO);
// Draw first cube.
float kTranslationMatrix[16];
FillTranslationMatrix(0.0f, 0.0f, 0.0f, kTranslationMatrix);
glUniformMatrix4fv(mMultiviewTranslationUniformLoc, 1, GL_TRUE, kTranslationMatrix);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mCube.indices.size()),
GL_UNSIGNED_SHORT, nullptr);
// Draw second cube.
FillTranslationMatrix(1.0f, 1.0f, -2.0f, kTranslationMatrix);
glUniformMatrix4fv(mMultiviewTranslationUniformLoc, 1, GL_TRUE, kTranslationMatrix);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mCube.indices.size()),
GL_UNSIGNED_SHORT, nullptr);
glBindVertexArray(0);
}
// Combine both views.
{
// Bind the default framebuffer object and clear.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Set the viewport to cover the whole screen.
glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight());
glUseProgram(mCombineProgram);
// Bind the 2D array texture to be used as a sampler.
glUniform1i(glGetUniformLocation(mCombineProgram, "uMultiviewTex"), 0);
glBindTexture(GL_TEXTURE_2D_ARRAY, mMultiviewFBO);
glActiveTexture(GL_TEXTURE0);
// Draw a quad which covers the whole screen. Layer and texture coordinates are
// calculated in the vertex shader based on the UV coordinates of the quad.
glBindVertexArray(mQuadVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
}
private:
GLuint mMultiviewProgram;
GLint mMultiviewPersperiveUniformLoc;
GLint mMultiviewLeftEyeCameraUniformLoc;
GLint mMultiviewRightEyeCameraUniformLoc;
GLint mMultiviewTranslationUniformLoc;
GLuint mMultiviewFBO;
GLuint mColorTexture;
GLuint mDepthTexture;
GLuint mQuadVAO;
GLuint mQuadVBO;
CubeGeometry mCube;
GLuint mCubeVAO;
GLuint mCubePosVBO;
GLuint mCubeNormalVBO;
GLuint mCubeIBO;
GLuint mCombineProgram;
};
int main(int argc, char **argv)
{
MultiviewSample app(argc, argv);
return app.run();
}