This repository has been archived by the owner on Aug 8, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[core] add ability to render to offscreen textures
- Loading branch information
Showing
9 changed files
with
245 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Binary file added
BIN
+4.49 KB
test/fixtures/offscreen_texture/render-to-fbo-composited/expected.png
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |