summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/core-files.cmake2
-rw-r--r--cmake/test-files.cmake1
-rw-r--r--src/mbgl/util/offscreen_texture.cpp66
-rw-r--r--src/mbgl/util/offscreen_texture.hpp23
-rw-r--r--test/fixtures/offscreen_texture/empty-red/expected.pngbin0 -> 2870 bytes
-rw-r--r--test/fixtures/offscreen_texture/render-to-fbo-composited/expected.pngbin0 -> 4599 bytes
-rw-r--r--test/fixtures/offscreen_texture/render-to-fbo/expected.pngbin0 -> 2870 bytes
-rw-r--r--test/fixtures/offscreen_texture/render-to-texture/expected.pngbin0 -> 728 bytes
-rw-r--r--test/util/offscreen_texture.cpp153
9 files changed, 245 insertions, 0 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index a69cd1b160..a885e182a1 100644
--- a/cmake/core-files.cmake
+++ b/cmake/core-files.cmake
@@ -456,6 +456,8 @@ set(MBGL_CORE_FILES
src/mbgl/util/mat4.hpp
src/mbgl/util/math.cpp
src/mbgl/util/math.hpp
+ src/mbgl/util/offscreen_texture.cpp
+ src/mbgl/util/offscreen_texture.hpp
src/mbgl/util/premultiply.cpp
src/mbgl/util/premultiply.hpp
src/mbgl/util/rapidjson.hpp
diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake
index 706d1bca59..e376bf098a 100644
--- a/cmake/test-files.cmake
+++ b/cmake/test-files.cmake
@@ -97,6 +97,7 @@ set(MBGL_TEST_FILES
test/util/memory.cpp
test/util/merge_lines.cpp
test/util/number_conversions.cpp
+ test/util/offscreen_texture.cpp
test/util/projection.cpp
test/util/run_loop.cpp
test/util/text_conversions.cpp
diff --git a/src/mbgl/util/offscreen_texture.cpp b/src/mbgl/util/offscreen_texture.cpp
new file mode 100644
index 0000000000..e077d60572
--- /dev/null
+++ b/src/mbgl/util/offscreen_texture.cpp
@@ -0,0 +1,66 @@
+#include <mbgl/gl/gl_config.hpp>
+#include <mbgl/util/offscreen_texture.hpp>
+
+#include <cassert>
+
+namespace mbgl {
+
+void OffscreenTexture::bind(gl::ObjectStore& store,
+ gl::Config& config,
+ std::array<uint16_t, 2> size) {
+ assert(size[0] > 0 && size[1] > 0);
+
+ if (raster.getSize() != size) {
+ raster.load(PremultipliedImage(size[0], size[1], nullptr));
+ raster.upload(store, config, 0);
+ }
+
+ if (!fbo) {
+ fbo = store.createFBO();
+ config.bindFramebuffer = *fbo;
+ MBGL_CHECK_ERROR(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ raster.getID(), 0));
+
+ GLenum status = MBGL_CHECK_ERROR(glCheckFramebufferStatus(GL_FRAMEBUFFER));
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ switch (status) {
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+ throw std::runtime_error("Couldn't create framebuffer: incomplete attachment");
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+ throw std::runtime_error(
+ "Couldn't create framebuffer: incomplete missing attachment");
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ throw std::runtime_error("Couldn't create framebuffer: incomplete draw buffer");
+#endif
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ throw std::runtime_error("Couldn't create framebuffer: incomplete read buffer");
+#endif
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+ throw std::runtime_error("Couldn't create framebuffer: incomplete dimensions");
+#endif
+
+ case GL_FRAMEBUFFER_UNSUPPORTED:
+ throw std::runtime_error("Couldn't create framebuffer: unsupported");
+ default:
+ throw std::runtime_error("Couldn't create framebuffer: other");
+ }
+ }
+ } else {
+ config.bindFramebuffer = *fbo;
+ }
+
+ config.viewport = { { 0, 0, static_cast<GLint>(size[0]), static_cast<GLint>(size[1]) } };
+}
+
+Raster& OffscreenTexture::getTexture() {
+ return raster;
+}
+
+std::array<uint16_t, 2> OffscreenTexture::getSize() const {
+ return raster.getSize();
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/util/offscreen_texture.hpp b/src/mbgl/util/offscreen_texture.hpp
new file mode 100644
index 0000000000..cceb938174
--- /dev/null
+++ b/src/mbgl/util/offscreen_texture.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <mbgl/util/raster.hpp>
+
+namespace mbgl {
+
+namespace gl {
+class Config;
+} // namespace gl
+
+class OffscreenTexture {
+public:
+ void bind(gl::ObjectStore&, gl::Config&, std::array<uint16_t, 2> size);
+
+ Raster& getTexture();
+ std::array<uint16_t, 2> getSize() const;
+
+private:
+ mbgl::optional<gl::UniqueFBO> fbo;
+ Raster raster;
+};
+
+} // namespace mbgl
diff --git a/test/fixtures/offscreen_texture/empty-red/expected.png b/test/fixtures/offscreen_texture/empty-red/expected.png
new file mode 100644
index 0000000000..7ecf05ca7c
--- /dev/null
+++ b/test/fixtures/offscreen_texture/empty-red/expected.png
Binary files differ
diff --git a/test/fixtures/offscreen_texture/render-to-fbo-composited/expected.png b/test/fixtures/offscreen_texture/render-to-fbo-composited/expected.png
new file mode 100644
index 0000000000..b4d2a0b1b6
--- /dev/null
+++ b/test/fixtures/offscreen_texture/render-to-fbo-composited/expected.png
Binary files differ
diff --git a/test/fixtures/offscreen_texture/render-to-fbo/expected.png b/test/fixtures/offscreen_texture/render-to-fbo/expected.png
new file mode 100644
index 0000000000..7ecf05ca7c
--- /dev/null
+++ b/test/fixtures/offscreen_texture/render-to-fbo/expected.png
Binary files differ
diff --git a/test/fixtures/offscreen_texture/render-to-texture/expected.png b/test/fixtures/offscreen_texture/render-to-texture/expected.png
new file mode 100644
index 0000000000..7773e5ab05
--- /dev/null
+++ b/test/fixtures/offscreen_texture/render-to-texture/expected.png
Binary files differ
diff --git a/test/util/offscreen_texture.cpp b/test/util/offscreen_texture.cpp
new file mode 100644
index 0000000000..74a616134e
--- /dev/null
+++ b/test/util/offscreen_texture.cpp
@@ -0,0 +1,153 @@
+#include <mbgl/test/util.hpp>
+
+#include <mbgl/gl/gl_config.hpp>
+#include <mbgl/platform/default/headless_view.hpp>
+
+#include <mbgl/util/offscreen_texture.hpp>
+#include <mbgl/util/raster.hpp>
+
+using namespace mbgl;
+
+TEST(OffscreenTexture, EmptyRed) {
+ HeadlessView view(1.0f, 512, 256);
+ view.activate();
+
+ MBGL_CHECK_ERROR(glClearColor(1.0f, 0.0f, 0.0f, 1.0f));
+ MBGL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT));
+
+ auto image = view.readStillImage();
+ test::checkImage("test/fixtures/offscreen_texture/empty-red", image, 0, 0);
+}
+
+struct Shader {
+ Shader(const GLchar* vertex, const GLchar* fragment) {
+ program = MBGL_CHECK_ERROR(glCreateProgram());
+ vertexShader = MBGL_CHECK_ERROR(glCreateShader(GL_VERTEX_SHADER));
+ fragmentShader = MBGL_CHECK_ERROR(glCreateShader(GL_FRAGMENT_SHADER));
+ MBGL_CHECK_ERROR(glShaderSource(vertexShader, 1, &vertex, nullptr));
+ MBGL_CHECK_ERROR(glCompileShader(vertexShader));
+ MBGL_CHECK_ERROR(glAttachShader(program, vertexShader));
+ MBGL_CHECK_ERROR(glShaderSource(fragmentShader, 1, &fragment, nullptr));
+ MBGL_CHECK_ERROR(glCompileShader(fragmentShader));
+ MBGL_CHECK_ERROR(glAttachShader(program, fragmentShader));
+ MBGL_CHECK_ERROR(glLinkProgram(program));
+ a_pos = glGetAttribLocation(program, "a_pos");
+ }
+
+ ~Shader() {
+ MBGL_CHECK_ERROR(glDetachShader(program, vertexShader));
+ MBGL_CHECK_ERROR(glDetachShader(program, fragmentShader));
+ MBGL_CHECK_ERROR(glDeleteShader(vertexShader));
+ MBGL_CHECK_ERROR(glDeleteShader(fragmentShader));
+ MBGL_CHECK_ERROR(glDeleteProgram(program));
+ }
+
+ GLuint program = 0;
+ GLuint vertexShader = 0;
+ GLuint fragmentShader = 0;
+ GLuint a_pos = 0;
+};
+
+struct Buffer {
+ Buffer(std::vector<GLfloat> data) {
+ MBGL_CHECK_ERROR(glGenBuffers(1, &buffer));
+ MBGL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+ MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(GLfloat), data.data(),
+ GL_STATIC_DRAW));
+ }
+
+ ~Buffer() {
+ MBGL_CHECK_ERROR(glDeleteBuffers(1, &buffer));
+ }
+
+ GLuint buffer = 0;
+};
+
+
+TEST(OffscreenTexture, RenderToTexture) {
+ HeadlessView view(1.0f, 512, 256);
+ view.activate();
+ gl::Config config;
+ gl::ObjectStore store;
+
+
+ MBGL_CHECK_ERROR(glEnable(GL_BLEND));
+ MBGL_CHECK_ERROR(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+
+ Shader paintShader(R"MBGL_SHADER(
+attribute vec2 a_pos;
+void main() {
+ gl_Position = vec4(a_pos, 0, 1);
+}
+)MBGL_SHADER", R"MBGL_SHADER(
+void main() {
+ gl_FragColor = vec4(0, 0.8, 0, 0.8);
+}
+)MBGL_SHADER");
+
+ Shader compositeShader(R"MBGL_SHADER(
+attribute vec2 a_pos;
+varying vec2 v_texcoord;
+void main() {
+ gl_Position = vec4(a_pos, 0, 1);
+ v_texcoord = (a_pos + 1.0) / 2.0;
+}
+)MBGL_SHADER", R"MBGL_SHADER(
+uniform sampler2D u_texture;
+varying vec2 v_texcoord;
+void main() {
+ gl_FragColor = texture2D(u_texture, v_texcoord);
+}
+)MBGL_SHADER");
+
+ GLuint u_texture = glGetUniformLocation(compositeShader.program, "u_texture");
+
+ Buffer triangleBuffer({ 0, 0.5, 0.5, -0.5, -0.5, -0.5 });
+ Buffer viewportBuffer({ -1, -1, 1, -1, -1, 1, 1, 1 });
+
+ // Make sure the texture gets destructed before we call store.reset();
+ {
+ // First, draw red to the bound FBO.
+ config.clearColor = { 1, 0, 0, 1 };
+ MBGL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT));
+
+ // Then, create a texture, bind it, and render yellow to that texture. This should not
+ // affect the originally bound FBO.
+ OffscreenTexture texture;
+ texture.bind(store, config, {{ 128, 128 }});
+
+ config.clearColor = { 0, 0, 0, 0 };
+ MBGL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT));
+
+ config.program = paintShader.program;
+ MBGL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, triangleBuffer.buffer));
+ MBGL_CHECK_ERROR(glEnableVertexAttribArray(paintShader.a_pos));
+ MBGL_CHECK_ERROR(
+ glVertexAttribPointer(paintShader.a_pos, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
+ MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, 3));
+
+ auto image = view.readStillImage(texture.getSize());
+ test::checkImage("test/fixtures/offscreen_texture/render-to-texture", image, 0, 0);
+
+ // Now reset the FBO back to normal and retrieve the original (restored) framebuffer.
+ config.reset();
+
+ image = view.readStillImage();
+ test::checkImage("test/fixtures/offscreen_texture/render-to-fbo", image, 0, 0);
+
+ // Now, composite the Framebuffer texture we've rendered to onto the main FBO.
+ config.program = compositeShader.program;
+ texture.getTexture().bind(store, config, 0, Raster::Scaling::Linear);
+ MBGL_CHECK_ERROR(glUniform1i(u_texture, 0));
+ MBGL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, viewportBuffer.buffer));
+ MBGL_CHECK_ERROR(glEnableVertexAttribArray(compositeShader.a_pos));
+ MBGL_CHECK_ERROR(
+ glVertexAttribPointer(compositeShader.a_pos, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
+ MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
+
+ image = view.readStillImage();
+ test::checkImage("test/fixtures/offscreen_texture/render-to-fbo-composited", image, 0, 0.1);
+ }
+
+ store.reset();
+}