// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.h" #include #include "base/basictypes.h" #include "gpu/command_buffer/service/gl_utils.h" #include "gpu/command_buffer/service/gles2_cmd_decoder.h" #define SHADER(src) \ "#ifdef GL_ES\n" \ "precision mediump float;\n" \ "#define TexCoordPrecision mediump\n" \ "#else\n" \ "#define TexCoordPrecision\n" \ "#endif\n" #src #define SHADER_2D(src) \ "#define SamplerType sampler2D\n" \ "#define TextureLookup texture2D\n" SHADER(src) #define SHADER_RECTANGLE_ARB(src) \ "#define SamplerType sampler2DRect\n" \ "#define TextureLookup texture2DRect\n" SHADER(src) #define SHADER_EXTERNAL_OES(src) \ "#extension GL_OES_EGL_image_external : require\n" \ "#define SamplerType samplerExternalOES\n" \ "#define TextureLookup texture2D\n" SHADER(src) #define FRAGMENT_SHADERS(src) \ SHADER_2D(src), SHADER_RECTANGLE_ARB(src), SHADER_EXTERNAL_OES(src) namespace { const GLfloat kIdentityMatrix[16] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; enum VertexShaderId { VERTEX_SHADER_COPY_TEXTURE, VERTEX_SHADER_COPY_TEXTURE_FLIP_Y, NUM_VERTEX_SHADERS, }; enum FragmentShaderId { FRAGMENT_SHADER_COPY_TEXTURE_2D, FRAGMENT_SHADER_COPY_TEXTURE_RECTANGLE_ARB, FRAGMENT_SHADER_COPY_TEXTURE_EXTERNAL_OES, FRAGMENT_SHADER_COPY_TEXTURE_PREMULTIPLY_ALPHA_2D, FRAGMENT_SHADER_COPY_TEXTURE_PREMULTIPLY_ALPHA_RECTANGLE_ARB, FRAGMENT_SHADER_COPY_TEXTURE_PREMULTIPLY_ALPHA_EXTERNAL_OES, FRAGMENT_SHADER_COPY_TEXTURE_UNPREMULTIPLY_ALPHA_2D, FRAGMENT_SHADER_COPY_TEXTURE_UNPREMULTIPLY_ALPHA_RECTANGLE_ARB, FRAGMENT_SHADER_COPY_TEXTURE_UNPREMULTIPLY_ALPHA_EXTERNAL_OES, NUM_FRAGMENT_SHADERS, }; const char* vertex_shader_source[NUM_VERTEX_SHADERS] = { // VERTEX_SHADER_COPY_TEXTURE SHADER( uniform vec2 u_vertex_translate; uniform vec2 u_half_size; attribute vec4 a_position; varying TexCoordPrecision vec2 v_uv; void main(void) { gl_Position = a_position + vec4(u_vertex_translate, 0.0, 0.0); v_uv = a_position.xy * vec2(u_half_size.s, u_half_size.t) + vec2(u_half_size.s, u_half_size.t); }), // VERTEX_SHADER_COPY_TEXTURE_FLIP_Y SHADER( uniform vec2 u_vertex_translate; uniform vec2 u_half_size; attribute vec4 a_position; varying TexCoordPrecision vec2 v_uv; void main(void) { gl_Position = a_position + vec4(u_vertex_translate, 0.0, 0.0); v_uv = a_position.xy * vec2(u_half_size.s, -u_half_size.t) + vec2(u_half_size.s, u_half_size.t); }), }; const char* fragment_shader_source[NUM_FRAGMENT_SHADERS] = { // FRAGMENT_SHADER_COPY_TEXTURE_* FRAGMENT_SHADERS( uniform SamplerType u_sampler; uniform mat4 u_tex_coord_transform; varying TexCoordPrecision vec2 v_uv; void main(void) { TexCoordPrecision vec4 uv = u_tex_coord_transform * vec4(v_uv, 0, 1); gl_FragColor = TextureLookup(u_sampler, uv.st); }), // FRAGMENT_SHADER_COPY_TEXTURE_PREMULTIPLY_ALPHA_* FRAGMENT_SHADERS( uniform SamplerType u_sampler; uniform mat4 u_tex_coord_transform; varying TexCoordPrecision vec2 v_uv; void main(void) { TexCoordPrecision vec4 uv = u_tex_coord_transform * vec4(v_uv, 0, 1); gl_FragColor = TextureLookup(u_sampler, uv.st); gl_FragColor.rgb *= gl_FragColor.a; }), // FRAGMENT_SHADER_COPY_TEXTURE_UNPREMULTIPLY_ALPHA_* FRAGMENT_SHADERS( uniform SamplerType u_sampler; uniform mat4 u_tex_coord_transform; varying TexCoordPrecision vec2 v_uv; void main(void) { TexCoordPrecision vec4 uv = u_tex_coord_transform * vec4(v_uv, 0, 1); gl_FragColor = TextureLookup(u_sampler, uv.st); if (gl_FragColor.a > 0.0) gl_FragColor.rgb /= gl_FragColor.a; }), }; // Returns the correct vertex shader id to evaluate the copy operation for // the CHROMIUM_flipy setting. VertexShaderId GetVertexShaderId(bool flip_y) { // bit 0: flip y static VertexShaderId shader_ids[] = { VERTEX_SHADER_COPY_TEXTURE, VERTEX_SHADER_COPY_TEXTURE_FLIP_Y, }; unsigned index = flip_y ? 1 : 0; return shader_ids[index]; } // Returns the correct fragment shader id to evaluate the copy operation for // the premultiply alpha pixel store settings and target. FragmentShaderId GetFragmentShaderId(bool premultiply_alpha, bool unpremultiply_alpha, GLenum target) { enum { SAMPLER_2D, SAMPLER_RECTANGLE_ARB, SAMPLER_EXTERNAL_OES, NUM_SAMPLERS }; // bit 0: premultiply alpha // bit 1: unpremultiply alpha static FragmentShaderId shader_ids[][NUM_SAMPLERS] = { { FRAGMENT_SHADER_COPY_TEXTURE_2D, FRAGMENT_SHADER_COPY_TEXTURE_RECTANGLE_ARB, FRAGMENT_SHADER_COPY_TEXTURE_EXTERNAL_OES, }, { FRAGMENT_SHADER_COPY_TEXTURE_PREMULTIPLY_ALPHA_2D, FRAGMENT_SHADER_COPY_TEXTURE_PREMULTIPLY_ALPHA_RECTANGLE_ARB, FRAGMENT_SHADER_COPY_TEXTURE_PREMULTIPLY_ALPHA_EXTERNAL_OES, }, { FRAGMENT_SHADER_COPY_TEXTURE_UNPREMULTIPLY_ALPHA_2D, FRAGMENT_SHADER_COPY_TEXTURE_UNPREMULTIPLY_ALPHA_RECTANGLE_ARB, FRAGMENT_SHADER_COPY_TEXTURE_UNPREMULTIPLY_ALPHA_EXTERNAL_OES, }, { FRAGMENT_SHADER_COPY_TEXTURE_2D, FRAGMENT_SHADER_COPY_TEXTURE_RECTANGLE_ARB, FRAGMENT_SHADER_COPY_TEXTURE_EXTERNAL_OES, }}; unsigned index = (premultiply_alpha ? (1 << 0) : 0) | (unpremultiply_alpha ? (1 << 1) : 0); switch (target) { case GL_TEXTURE_2D: return shader_ids[index][SAMPLER_2D]; case GL_TEXTURE_RECTANGLE_ARB: return shader_ids[index][SAMPLER_RECTANGLE_ARB]; case GL_TEXTURE_EXTERNAL_OES: return shader_ids[index][SAMPLER_EXTERNAL_OES]; default: break; } NOTREACHED(); return shader_ids[0][SAMPLER_2D]; } void CompileShader(GLuint shader, const char* shader_source) { glShaderSource(shader, 1, &shader_source, 0); glCompileShader(shader); #ifndef NDEBUG GLint compile_status; glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status); if (GL_TRUE != compile_status) DLOG(ERROR) << "CopyTextureCHROMIUM: shader compilation failure."; #endif } void DeleteShader(GLuint shader) { if (shader) glDeleteShader(shader); } bool BindFramebufferTexture2D(GLenum target, GLuint texture_id, GLuint framebuffer) { DCHECK(target == GL_TEXTURE_2D || target == GL_TEXTURE_RECTANGLE_ARB); glActiveTexture(GL_TEXTURE0); glBindTexture(target, texture_id); // NVidia drivers require texture settings to be a certain way // or they won't report FRAMEBUFFER_COMPLETE. glTexParameterf(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer); glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, texture_id, 0); #ifndef NDEBUG GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER); if (GL_FRAMEBUFFER_COMPLETE != fb_status) { DLOG(ERROR) << "CopyTextureCHROMIUM: Incomplete framebuffer."; return false; } #endif return true; } void DoCopyTexImage2D(const gpu::gles2::GLES2Decoder* decoder, GLenum source_target, GLuint source_id, GLuint dest_id, GLenum dest_internal_format, GLsizei width, GLsizei height, GLuint framebuffer) { DCHECK(source_target == GL_TEXTURE_2D || source_target == GL_TEXTURE_RECTANGLE_ARB); if (BindFramebufferTexture2D(source_target, source_id, framebuffer)) { glBindTexture(GL_TEXTURE_2D, dest_id); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glCopyTexImage2D(GL_TEXTURE_2D, 0 /* level */, dest_internal_format, 0 /* x */, 0 /* y */, width, height, 0 /* border */); } decoder->RestoreTextureState(source_id); decoder->RestoreTextureState(dest_id); decoder->RestoreTextureUnitBindings(0); decoder->RestoreActiveTexture(); decoder->RestoreFramebufferBindings(); } void DoCopyTexSubImage2D(const gpu::gles2::GLES2Decoder* decoder, GLenum source_target, GLuint source_id, GLuint dest_id, GLint xoffset, GLint yoffset, GLint source_x, GLint source_y, GLsizei source_width, GLsizei source_height, GLuint framebuffer) { DCHECK(source_target == GL_TEXTURE_2D || source_target == GL_TEXTURE_RECTANGLE_ARB); if (BindFramebufferTexture2D(source_target, source_id, framebuffer)) { glBindTexture(GL_TEXTURE_2D, dest_id); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glCopyTexSubImage2D(GL_TEXTURE_2D, 0 /* level */, xoffset, yoffset, source_x, source_y, source_width, source_height); } decoder->RestoreTextureState(source_id); decoder->RestoreTextureState(dest_id); decoder->RestoreTextureUnitBindings(0); decoder->RestoreActiveTexture(); decoder->RestoreFramebufferBindings(); } } // namespace namespace gpu { CopyTextureCHROMIUMResourceManager::CopyTextureCHROMIUMResourceManager() : initialized_(false), vertex_shaders_(NUM_VERTEX_SHADERS, 0u), fragment_shaders_(NUM_FRAGMENT_SHADERS, 0u), buffer_id_(0u), framebuffer_(0u) {} CopyTextureCHROMIUMResourceManager::~CopyTextureCHROMIUMResourceManager() { // |buffer_id_| and |framebuffer_| can be not-null because when GPU context is // lost, this class can be deleted without releasing resources like // GLES2DecoderImpl. } void CopyTextureCHROMIUMResourceManager::Initialize( const gles2::GLES2Decoder* decoder) { static_assert( kVertexPositionAttrib == 0u, "kVertexPositionAttrib must be 0"); DCHECK(!buffer_id_); DCHECK(!framebuffer_); DCHECK(programs_.empty()); // Initialize all of the GPU resources required to perform the copy. glGenBuffersARB(1, &buffer_id_); glBindBuffer(GL_ARRAY_BUFFER, buffer_id_); const GLfloat kQuadVertices[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; glBufferData( GL_ARRAY_BUFFER, sizeof(kQuadVertices), kQuadVertices, GL_STATIC_DRAW); glGenFramebuffersEXT(1, &framebuffer_); decoder->RestoreBufferBindings(); initialized_ = true; } void CopyTextureCHROMIUMResourceManager::Destroy() { if (!initialized_) return; glDeleteFramebuffersEXT(1, &framebuffer_); framebuffer_ = 0; std::for_each(vertex_shaders_.begin(), vertex_shaders_.end(), DeleteShader); std::for_each( fragment_shaders_.begin(), fragment_shaders_.end(), DeleteShader); for (ProgramMap::const_iterator it = programs_.begin(); it != programs_.end(); ++it) { const ProgramInfo& info = it->second; glDeleteProgram(info.program); } glDeleteBuffersARB(1, &buffer_id_); buffer_id_ = 0; } void CopyTextureCHROMIUMResourceManager::DoCopyTexture( const gles2::GLES2Decoder* decoder, GLenum source_target, GLuint source_id, GLenum source_internal_format, GLuint dest_id, GLenum dest_internal_format, GLsizei width, GLsizei height, bool flip_y, bool premultiply_alpha, bool unpremultiply_alpha) { bool premultiply_alpha_change = premultiply_alpha ^ unpremultiply_alpha; // GL_INVALID_OPERATION is generated if the currently bound framebuffer's // format does not contain a superset of the components required by the base // format of internalformat. // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glCopyTexImage2D.xml bool source_format_contain_superset_of_dest_format = (source_internal_format == dest_internal_format && source_internal_format != GL_BGRA_EXT) || (source_internal_format == GL_RGBA && dest_internal_format == GL_RGB); // GL_TEXTURE_RECTANGLE_ARB on FBO is supported by OpenGL, not GLES2, // so restrict this to GL_TEXTURE_2D. if (source_target == GL_TEXTURE_2D && !flip_y && !premultiply_alpha_change && source_format_contain_superset_of_dest_format) { DoCopyTexImage2D(decoder, source_target, source_id, dest_id, dest_internal_format, width, height, framebuffer_); return; } // Use kIdentityMatrix if no transform passed in. DoCopyTextureWithTransform(decoder, source_target, source_id, dest_id, width, height, flip_y, premultiply_alpha, unpremultiply_alpha, kIdentityMatrix); } void CopyTextureCHROMIUMResourceManager::DoCopySubTexture( const gles2::GLES2Decoder* decoder, GLenum source_target, GLuint source_id, GLenum source_internal_format, GLuint dest_id, GLenum dest_internal_format, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, GLsizei dest_width, GLsizei dest_height, GLsizei source_width, GLsizei source_height, bool flip_y, bool premultiply_alpha, bool unpremultiply_alpha) { bool premultiply_alpha_change = premultiply_alpha ^ unpremultiply_alpha; // GL_INVALID_OPERATION is generated if the currently bound framebuffer's // format does not contain a superset of the components required by the base // format of internalformat. // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glCopyTexImage2D.xml bool source_format_contain_superset_of_dest_format = (source_internal_format == dest_internal_format && source_internal_format != GL_BGRA_EXT) || (source_internal_format == GL_RGBA && dest_internal_format == GL_RGB); // GL_TEXTURE_RECTANGLE_ARB on FBO is supported by OpenGL, not GLES2, // so restrict this to GL_TEXTURE_2D. if (source_target == GL_TEXTURE_2D && !flip_y && !premultiply_alpha_change && source_format_contain_superset_of_dest_format) { DoCopyTexSubImage2D(decoder, source_target, source_id, dest_id, xoffset, yoffset, x, y, width, height, framebuffer_); return; } DoCopyTextureInternal(decoder, source_target, source_id, dest_id, xoffset, yoffset, x, y, width, height, dest_width, dest_height, source_width, source_height, flip_y, premultiply_alpha, unpremultiply_alpha, kIdentityMatrix); } void CopyTextureCHROMIUMResourceManager::DoCopyTextureWithTransform( const gles2::GLES2Decoder* decoder, GLenum source_target, GLuint source_id, GLuint dest_id, GLsizei width, GLsizei height, bool flip_y, bool premultiply_alpha, bool unpremultiply_alpha, const GLfloat transform_matrix[16]) { GLsizei dest_width = width; GLsizei dest_height = height; DoCopyTextureInternal(decoder, source_target, source_id, dest_id, 0, 0, 0, 0, width, height, dest_width, dest_height, width, height, flip_y, premultiply_alpha, unpremultiply_alpha, transform_matrix); } void CopyTextureCHROMIUMResourceManager::DoCopyTextureInternal( const gles2::GLES2Decoder* decoder, GLenum source_target, GLuint source_id, GLuint dest_id, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, GLsizei dest_width, GLsizei dest_height, GLsizei source_width, GLsizei source_height, bool flip_y, bool premultiply_alpha, bool unpremultiply_alpha, const GLfloat transform_matrix[16]) { DCHECK(source_target == GL_TEXTURE_2D || source_target == GL_TEXTURE_RECTANGLE_ARB || source_target == GL_TEXTURE_EXTERNAL_OES); DCHECK(xoffset >= 0 && xoffset + source_width <= dest_width); DCHECK(yoffset >= 0 && yoffset + source_height <= dest_height); if (!initialized_) { DLOG(ERROR) << "CopyTextureCHROMIUM: Uninitialized manager."; return; } VertexShaderId vertex_shader_id = GetVertexShaderId(flip_y); DCHECK_LT(static_cast(vertex_shader_id), vertex_shaders_.size()); FragmentShaderId fragment_shader_id = GetFragmentShaderId( premultiply_alpha, unpremultiply_alpha, source_target); DCHECK_LT(static_cast(fragment_shader_id), fragment_shaders_.size()); ProgramMapKey key(vertex_shader_id, fragment_shader_id); ProgramInfo* info = &programs_[key]; // Create program if necessary. if (!info->program) { info->program = glCreateProgram(); GLuint* vertex_shader = &vertex_shaders_[vertex_shader_id]; if (!*vertex_shader) { *vertex_shader = glCreateShader(GL_VERTEX_SHADER); CompileShader(*vertex_shader, vertex_shader_source[vertex_shader_id]); } glAttachShader(info->program, *vertex_shader); GLuint* fragment_shader = &fragment_shaders_[fragment_shader_id]; if (!*fragment_shader) { *fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); CompileShader(*fragment_shader, fragment_shader_source[fragment_shader_id]); } glAttachShader(info->program, *fragment_shader); glBindAttribLocation(info->program, kVertexPositionAttrib, "a_position"); glLinkProgram(info->program); #ifndef NDEBUG GLint linked; glGetProgramiv(info->program, GL_LINK_STATUS, &linked); if (!linked) DLOG(ERROR) << "CopyTextureCHROMIUM: program link failure."; #endif info->vertex_translate_handle = glGetUniformLocation(info->program, "u_vertex_translate"); info->tex_coord_transform_handle = glGetUniformLocation(info->program, "u_tex_coord_transform"); info->half_size_handle = glGetUniformLocation(info->program, "u_half_size"); info->sampler_handle = glGetUniformLocation(info->program, "u_sampler"); } glUseProgram(info->program); glUniformMatrix4fv(info->tex_coord_transform_handle, 1, GL_FALSE, transform_matrix); GLint x_translate = xoffset - x; GLint y_translate = yoffset - y; if (!x_translate && !y_translate) { glUniform2f(info->vertex_translate_handle, 0.0f, 0.0f); } else { // transform offsets from ([0, dest_width], [0, dest_height]) coord. // to ([-1, 1], [-1, 1]) coord. GLfloat x_translate_on_vertex = ((2.f * x_translate) / dest_width); GLfloat y_translate_on_vertex = ((2.f * y_translate) / dest_height); // Pass translation to the shader program. glUniform2f(info->vertex_translate_handle, x_translate_on_vertex, y_translate_on_vertex); } if (source_target == GL_TEXTURE_RECTANGLE_ARB) glUniform2f(info->half_size_handle, source_width / 2.0f, source_height / 2.0f); else glUniform2f(info->half_size_handle, 0.5f, 0.5f); if (BindFramebufferTexture2D(GL_TEXTURE_2D, dest_id, framebuffer_)) { #ifndef NDEBUG // glValidateProgram of MACOSX validates FBO unlike other platforms, so // glValidateProgram must be called after FBO binding. crbug.com/463439 glValidateProgram(info->program); GLint validation_status; glGetProgramiv(info->program, GL_VALIDATE_STATUS, &validation_status); if (GL_TRUE != validation_status) { DLOG(ERROR) << "CopyTextureCHROMIUM: Invalid shader."; return; } #endif decoder->ClearAllAttributes(); glEnableVertexAttribArray(kVertexPositionAttrib); glBindBuffer(GL_ARRAY_BUFFER, buffer_id_); glVertexAttribPointer(kVertexPositionAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0); glUniform1i(info->sampler_handle, 0); glBindTexture(source_target, source_id); glTexParameterf(source_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(source_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(source_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(source_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); glDisable(GL_CULL_FACE); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDepthMask(GL_FALSE); glDisable(GL_BLEND); bool need_scissor = xoffset || yoffset || width != dest_width || height != dest_height; if (need_scissor) { glEnable(GL_SCISSOR_TEST); glScissor(xoffset, yoffset, width, height); } else { glDisable(GL_SCISSOR_TEST); } glViewport(0, 0, dest_width, dest_height); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } decoder->RestoreAllAttributes(); decoder->RestoreTextureState(source_id); decoder->RestoreTextureState(dest_id); decoder->RestoreTextureUnitBindings(0); decoder->RestoreActiveTexture(); decoder->RestoreProgramBindings(); decoder->RestoreBufferBindings(); decoder->RestoreFramebufferBindings(); decoder->RestoreGlobalState(); } } // namespace gpu