package com.suno.android.common_ui.graphics import Vector3 import android.content.Context import android.opengl.GLES32 import android.opengl.GLSurfaceView import java.io.BufferedReader import java.io.InputStreamReader import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.FloatBuffer import javax.microedition.khronos.egl.EGLConfig import javax.microedition.khronos.opengles.GL10 class GLRenderer( private val context: Context, ) : GLSurfaceView.Renderer { private var program: Int = 0 private var startTime: Long = System.currentTimeMillis() private var displayWidth: Float = 1080f private var displayHeight: Float = 1920f private val vertices = floatArrayOf( -1.0f, 1.0f, // top-left -1.0f, -1.0f, // bottom-left 1.0f, 1.0f, // top-right 1.0f, -1.0f, // bottom-right ) private lateinit var vertexBuffer: FloatBuffer override fun onSurfaceCreated( gl: GL10?, config: EGLConfig?, ) { val vertexShaderCode = loadShaderFromAssets(context, "vertex_shader.glsl") val fragmentShaderCode = loadShaderFromAssets(context, "animated_aura_grainy_fullscreen.glsl") program = createProgram(vertexShaderCode, fragmentShaderCode) GLES32.glUseProgram(program) vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().apply { put(vertices) position(0) } GLES32.glClearColor(0.0f, 0.0f, 0.0f, 1.0f) } override fun onDrawFrame( gl: GL10?, ) { GLES32.glClear(GLES32.GL_COLOR_BUFFER_BIT or GLES32.GL_DEPTH_BUFFER_BIT) // Colors for blending (purple, orange, red, darker red) val backgroundColor = Vector3(0.5f, 0.0f, 0.5f) // Purple val colorA = Vector3(1.0f, 0.5f, 0.0f) // Orange val colorB = Vector3(1.0f, 0.0f, 0.0f) // Red val colorC = Vector3(0.8f, 0.1f, 0.1f) // Darker red // Time to drive animations val currentTime = (System.currentTimeMillis() - startTime) / 1000f // Pass parameters to the shader GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_time"), currentTime) GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_fractal_noise_z"), 0.3f) GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_grain_noise_scale"), 0.1f) GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_mix_point_start"), -0.2f) GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_mix_point_background"), -0.1f) GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_mix_point_backgroundA"), 0.0f) GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_mix_point_AB"), 0.2f) GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_mix_point_BC"), 0.4f) GLES32.glUniform1f(GLES32.glGetUniformLocation(program, "u_mix_point_end"), 2.0f) GLES32.glUniform3f( GLES32.glGetUniformLocation(program, "u_background_color"), backgroundColor.x, backgroundColor.y, backgroundColor.z, ) GLES32.glUniform3f(GLES32.glGetUniformLocation(program, "u_colorA"), colorA.x, colorA.y, colorA.z) GLES32.glUniform3f(GLES32.glGetUniformLocation(program, "u_colorB"), colorB.x, colorB.y, colorB.z) GLES32.glUniform3f(GLES32.glGetUniformLocation(program, "u_colorC"), colorC.x, colorC.y, colorC.z) GLES32.glUniform2f(GLES32.glGetUniformLocation(program, "u_display_size"), displayWidth, displayHeight) // Draw the rectangle (full-screen quad) val positionHandle = GLES32.glGetAttribLocation(program, "vPosition") GLES32.glEnableVertexAttribArray(positionHandle) GLES32.glVertexAttribPointer(positionHandle, 2, GLES32.GL_FLOAT, false, 0, vertexBuffer) GLES32.glDrawArrays(GLES32.GL_TRIANGLE_STRIP, 0, 4) GLES32.glDisableVertexAttribArray(positionHandle) } override fun onSurfaceChanged( gl: GL10?, width: Int, height: Int, ) { GLES32.glViewport(0, 0, width, height) displayWidth = width.toFloat() displayHeight = height.toFloat() } private fun createProgram( vertexShaderCode: String, fragmentShaderCode: String, ): Int { val vertexShader = loadShader(GLES32.GL_VERTEX_SHADER, vertexShaderCode) val fragmentShader = loadShader(GLES32.GL_FRAGMENT_SHADER, fragmentShaderCode) return GLES32.glCreateProgram().also { GLES32.glAttachShader(it, vertexShader) GLES32.glAttachShader(it, fragmentShader) GLES32.glLinkProgram(it) } } private fun loadShader( type: Int, shaderCode: String, ): Int = GLES32.glCreateShader(type).also { shader -> GLES32.glShaderSource(shader, shaderCode) GLES32.glCompileShader(shader) } private fun loadShaderFromAssets( context: Context, filename: String, ): String { val inputStream = context.assets.open(filename) val reader = BufferedReader(InputStreamReader(inputStream)) return reader.use { it.readText() } } }