Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[core] add ability to render to offscreen textures
Browse files Browse the repository at this point in the history
  • Loading branch information
kkaefer committed Sep 27, 2016
1 parent 68ec9d3 commit c88ce2d
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cmake/core-files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions cmake/test-files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions src/mbgl/util/offscreen_texture.cpp
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions src/mbgl/util/offscreen_texture.hpp
Original file line number Diff line number Diff line change
@@ -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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
153 changes: 153 additions & 0 deletions test/util/offscreen_texture.cpp
Original file line number Diff line number Diff line change
@@ -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();
}

0 comments on commit c88ce2d

Please sign in to comment.