// SPDX-License-Identifier: GPL-2.0-or-later #include "gl_frames.h" #include #include #include #include "core_process.h" #include "log.h" namespace { /// How gralloc describes a buffer; the first file descriptor is the dma-buf with the pixels. struct NativeHandle { int version; int num_fds; int num_ints; int data[1]; }; GLuint CompileShader(GLenum type, const char* source) { const GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, nullptr); glCompileShader(shader); GLint ok = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (ok != GL_TRUE) { char log[512]; glGetShaderInfoLog(shader, sizeof(log), nullptr, log); LOGE("shader compilation failed: %s", log); } return shader; } const char* const BlitVertexShader = R"(#version 300 es out vec2 uv; void main() { vec2 corner = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2)); // The frame's first row is its top row, GL's first row is the bottom one. uv = vec2(corner.x, 1.0 - corner.y); gl_Position = vec4(corner * 2.0 - 1.0, 0.0, 1.0); } )"; const char* const BlitFragmentShader = R"(#version 300 es precision highp float; uniform sampler2D frame; uniform int swap_red_blue; uniform int decode_srgb; in vec2 uv; out vec4 color; void main() { vec3 c = texture(frame, uv).rgb; if (swap_red_blue != 0) { c = c.bgr; } // The emulator delivers display-ready values. An sRGB target encodes whatever is written to // it, so those have to be made linear first. if (decode_srgb != 0) { c = mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c)); } color = vec4(c, 1.0); } )"; } // namespace bool GlContext::Create() { display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (display == EGL_NO_DISPLAY || !eglInitialize(display, nullptr, nullptr)) { LOGE("eglInitialize failed"); return false; } const EGLint config_attributes[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_NONE, }; EGLint num_configs = 0; if (!eglChooseConfig(display, config_attributes, &config, 1, &num_configs) || num_configs < 1) { LOGE("eglChooseConfig failed"); return false; } const EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes); // Nothing is drawn to this surface, the context just needs one to be current. const EGLint surface_attributes[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE}; surface = eglCreatePbufferSurface(display, config, surface_attributes); if (context == EGL_NO_CONTEXT || surface == EGL_NO_SURFACE || !eglMakeCurrent(display, surface, surface, context)) { LOGE("unable to create the GL context: 0x%x", eglGetError()); return false; } return true; } void GlContext::Destroy() { if (display == EGL_NO_DISPLAY) { return; } eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (surface != EGL_NO_SURFACE) { eglDestroySurface(display, surface); } if (context != EGL_NO_CONTEXT) { eglDestroyContext(display, context); } eglTerminate(display); display = EGL_NO_DISPLAY; surface = EGL_NO_SURFACE; context = EGL_NO_CONTEXT; } bool FrameBuffers::Create(EGLDisplay display, uint32_t width, uint32_t height, CoreProcess& core) { using GetNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*); const auto get_native_handle = reinterpret_cast( dlsym(RTLD_DEFAULT, "AHardwareBuffer_getNativeHandle")); if (get_native_handle == nullptr) { LOGE("AHardwareBuffer_getNativeHandle is not available"); return false; } AHardwareBuffer_Desc description{}; description.width = width; description.height = height; description.layers = 1; description.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; // The CPU bits keep the buffer in a plain row-by-row layout, which is what the emulator's // GPU driver can attach to and what its fallback path writes. description.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER | AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY; std::vector fds; uint32_t stride = 0; for (uint32_t i = 0; i < Count; ++i) { AHardwareBuffer* buffer = nullptr; if (AHardwareBuffer_allocate(&description, &buffer) != 0) { LOGE("unable to allocate frame buffer %u", i); return false; } buffers.push_back(buffer); AHardwareBuffer_Desc actual{}; AHardwareBuffer_describe(buffer, &actual); stride = actual.stride; const NativeHandle* handle = get_native_handle(buffer); if (handle == nullptr || handle->num_fds < 1) { LOGE("frame buffer %u has no file descriptor", i); return false; } fds.push_back(handle->data[0]); const EGLClientBuffer client_buffer = eglGetNativeClientBufferANDROID(buffer); const EGLint image_attributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; const EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, client_buffer, image_attributes); if (image == EGL_NO_IMAGE_KHR) { LOGE("unable to wrap frame buffer %u in an EGL image: 0x%x", i, eglGetError()); return false; } images.push_back(image); GLuint texture = 0; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); textures.push_back(texture); } LOGI("frame buffers: %u x %ux%u, stride %u", Count, width, height, stride); core.SetBuffers(fds, width, height, stride); return true; } void FrameBuffers::Destroy(EGLDisplay display) { for (const GLuint texture : textures) { glDeleteTextures(1, &texture); } for (const EGLImageKHR image : images) { eglDestroyImageKHR(display, image); } for (AHardwareBuffer* buffer : buffers) { AHardwareBuffer_release(buffer); } textures.clear(); images.clear(); buffers.clear(); } bool FrameBlitter::Create() { const GLuint vertex = CompileShader(GL_VERTEX_SHADER, BlitVertexShader); const GLuint fragment = CompileShader(GL_FRAGMENT_SHADER, BlitFragmentShader); program = glCreateProgram(); glAttachShader(program, vertex); glAttachShader(program, fragment); glLinkProgram(program); glDeleteShader(vertex); glDeleteShader(fragment); GLint linked = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &linked); if (linked != GL_TRUE) { LOGE("unable to link the blit program"); return false; } uniform_swap = glGetUniformLocation(program, "swap_red_blue"); uniform_decode = glGetUniformLocation(program, "decode_srgb"); glGenVertexArrays(1, &vertex_array); const char* extensions = reinterpret_cast(glGetString(GL_EXTENSIONS)); write_control = extensions != nullptr && std::strstr(extensions, "GL_EXT_sRGB_write_control") != nullptr; return true; } void FrameBlitter::Destroy() { if (program != 0) { glDeleteProgram(program); program = 0; } if (vertex_array != 0) { glDeleteVertexArrays(1, &vertex_array); vertex_array = 0; } } void FrameBlitter::Draw(GLuint texture, uint32_t width, uint32_t height, bool swap_red_blue, bool srgb_target) const { glViewport(0, 0, static_cast(width), static_cast(height)); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); const bool unencoded = srgb_target && write_control; if (unencoded) { glDisable(GL_FRAMEBUFFER_SRGB_EXT); } glUseProgram(program); glUniform1i(uniform_swap, swap_red_blue ? 1 : 0); glUniform1i(uniform_decode, srgb_target && !write_control ? 1 : 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glBindVertexArray(vertex_array); glDrawArrays(GL_TRIANGLES, 0, 3); if (unencoded) { // As it is by default: everything else drawn to such a target counts on it. glEnable(GL_FRAMEBUFFER_SRGB_EXT); } }