#include #include #include #include #include #include #include #include #include #include using namespace mbgl; using namespace mbgl::platform; TEST(OffscreenTexture, EmptyRed) { if (gfx::Backend::GetType() != gfx::Backend::Type::OpenGL) { return; } gl::HeadlessBackend backend({ 512, 256 }); gfx::BackendScope scope { backend }; // Scissor test shouldn't leak after gl::HeadlessBackend::bind(). MBGL_CHECK_ERROR(glScissor(64, 64, 128, 128)); static_cast(backend.getContext()).scissorTest.setCurrentValue(true); backend.getDefaultRenderable().getResource().bind(); MBGL_CHECK_ERROR(glClearColor(1.0f, 0.0f, 0.0f, 1.0f)); MBGL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT)); auto image = backend.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 = MBGL_CHECK_ERROR(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 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) { if (gfx::Backend::GetType() != gfx::Backend::Type::OpenGL) { return; } gl::HeadlessBackend backend({ 512, 256 }); gfx::BackendScope scope { backend }; auto& context = static_cast(backend.getContext()); MBGL_CHECK_ERROR(glEnable(GL_BLEND)); MBGL_CHECK_ERROR(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); Shader paintShader(R"MBGL_SHADER( #ifdef GL_ES precision mediump float; #endif attribute vec2 a_pos; void main() { gl_Position = vec4(a_pos, 0, 1); } )MBGL_SHADER", R"MBGL_SHADER( #ifdef GL_ES precision mediump float; #endif void main() { gl_FragColor = vec4(0, 0.8, 0, 0.8); } )MBGL_SHADER"); Shader compositeShader(R"MBGL_SHADER( #ifdef GL_ES precision mediump float; #endif 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( #ifdef GL_ES precision mediump float; #endif uniform sampler2D u_texture; varying vec2 v_texcoord; void main() { gl_FragColor = texture2D(u_texture, v_texcoord); } )MBGL_SHADER"); GLuint u_texture = MBGL_CHECK_ERROR(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 }); backend.getDefaultRenderable().getResource().bind(); // First, draw red to the bound FBO. context.clear(Color::red(), {}, {}); // Then, create a texture, bind it, and render yellow to that texture. This should not // affect the originally bound FBO. gl::OffscreenTexture texture(context, { 128, 128 }); // Scissor test shouldn't leak after OffscreenTexture::bind(). MBGL_CHECK_ERROR(glScissor(32, 32, 64, 64)); context.scissorTest.setCurrentValue(true); texture.getResource().bind(); context.clear(Color(), {}, {}); MBGL_CHECK_ERROR(glUseProgram(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 = texture.readStillImage(); 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. backend.getDefaultRenderable().getResource().bind(); image = backend.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. gl::bindTexture(context, 0, { texture.getTexture().getResource(), gfx::TextureFilterType::Linear }); MBGL_CHECK_ERROR(glUseProgram(compositeShader.program)); 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 = backend.readStillImage(); test::checkImage("test/fixtures/offscreen_texture/render-to-fbo-composited", image, 0, 0.1); } TEST(OffscreenTexture, ClearRenderPassColor) { auto backend = gfx::HeadlessBackend::Create({ 128, 256 }); gfx::BackendScope scope { *backend->getRendererBackend() }; auto& context = backend->getRendererBackend()->getContext(); auto encoder = context.createCommandEncoder(); auto offscreenTexture = context.createOffscreenTexture({128, 256}, gfx::TextureChannelDataType::UnsignedByte); auto renderPass = encoder->createRenderPass( "offscreen texture", { *offscreenTexture, Color{ 1.0f, 0.0f, 0.0f, 1.0f }, {}, {} }); renderPass.reset(); encoder.reset(); auto image = offscreenTexture->readStillImage(); test::checkImage("test/fixtures/offscreen_texture/clear-render-pass-color", image, 0, 0); }