package me.eater.threedom.gl import me.eater.threedom.gl.GL.stack import me.eater.threedom.gl.dom.ICamera import me.eater.threedom.gl.exception.ShaderProgramLinkingException import me.eater.threedom.gl.texture.Texture import me.eater.threedom.utils.joml.toFloat import org.joml.* import org.lwjgl.opengl.GL20.* import org.lwjgl.system.MemoryUtil import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty open class ShaderProgram(private val programId: Int = glCreateProgram()) { private val uniforms: MutableMap = mutableMapOf() val queuedUniformValues: MutableMap Unit> = mutableMapOf() private val textures: MutableMap = mutableMapOf() private var textureIndex: Int = 0 var model by mat4() var normalModel by mat3() var view by mat4() var projection by mat4() private fun getUniformLocation(name: String) = uniforms.getOrPut(name) { glGetUniformLocation(programId, name) } protected fun sampler2D( index: Int = textureIndex++, name: String? = null ): ReadWriteProperty { var setUniform = false return object : ReadWriteProperty { override fun getValue(thisRef: ShaderProgram, property: KProperty<*>): Texture? { return Texture.getTexture(textures[index] ?: return null) } override fun setValue(thisRef: ShaderProgram, property: KProperty<*>, value: Texture?) { textures[index] = value?.id ?: 0 if (setUniform) { return } val loc = getUniformLocation(name ?: property.name) if (loc == -1) { return } if (glGetInteger(GL_CURRENT_PROGRAM) == thisRef.programId) { glUniform1i(loc, index) } else { queuedUniformValues[loc] = { glUniform1i(loc, index) } } setUniform = true } } } protected fun uniform( name: String? = null, setter: (location: Int, value: T) -> Unit, getter: (programId: Int, location: Int) -> T? ) = UniformProperty(name, setter, getter) open class UniformProperty( val name: String? = null, val setter: (location: Int, value: T) -> Unit, val getter: (programId: Int, location: Int) -> T? ) { operator fun getValue(thisRef: ShaderProgram, property: KProperty<*>): T? { val loc = thisRef.getUniformLocation(name ?: property.name) if (loc == -1) { return null } return getter(thisRef.programId, loc) } operator fun setValue(thisRef: ShaderProgram, property: KProperty<*>, value: T?) { val valueNonNull = value ?: return val loc = thisRef.getUniformLocation(name ?: property.name) if (loc == -1) { return } if (glGetInteger(GL_CURRENT_PROGRAM) == thisRef.programId) { setter(loc, valueNonNull) } else { thisRef.queuedUniformValues[loc] = { setter(loc, valueNonNull) } } } } protected fun float(name: String? = null): UniformProperty = uniform(name, ::glUniform1f, ::glGetUniformf) protected fun int(name: String? = null): UniformProperty = uniform(name, ::glUniform1i, ::glGetUniformi) protected fun vec2(name: String? = null): UniformProperty = uniform(name, { loc, value -> val floatArray = MemoryUtil.memAllocFloat(2) value.get(floatArray) glUniform2fv(loc, floatArray) MemoryUtil.memFree(floatArray) }, { programId, location -> val buffer = FloatArray(2) glGetUniformfv(programId, location, buffer) Vector2f().set(buffer) }) protected fun vec3(name: String? = null): UniformProperty = uniform(name, { loc, value -> val floatArray = MemoryUtil.memAllocFloat(3) value.get(floatArray) glUniform3fv(loc, floatArray) MemoryUtil.memFree(floatArray) }, { programId, location -> val buffer = FloatArray(3) glGetUniformfv(programId, location, buffer) Vector3f().set(buffer) }) protected fun vec4(name: String? = null): UniformProperty = uniform(name, { loc, value -> val floatArray = MemoryUtil.memAllocFloat(4) value.get(floatArray) glUniform4fv(loc, floatArray) MemoryUtil.memFree(floatArray) }, { programId, location -> val buffer = FloatArray(4) glGetUniformfv(programId, location, buffer) Vector4f().set(buffer) }) protected fun mat3(name: String? = null): UniformProperty = uniform(name, { loc, value -> val floatArray = FloatArray(3 * 3) value.get(floatArray) glUniformMatrix3fv(loc, false, floatArray) }, { programId, location -> val buffer = FloatArray(3 * 3) glGetUniformfv(programId, location, buffer) Matrix3f().set(buffer) }) protected fun bool(name: String? = null): UniformProperty = uniform(name, { loc, value -> glUniform1i(loc, if (value) 1 else 0) }, { programId, location -> glGetUniformi(programId, location) == 1 }) protected fun mat4(name: String? = null): UniformProperty = uniform(name, { loc, value -> val floatArray = FloatArray(4 * 4) value.get(floatArray) glUniformMatrix4fv(loc, false, floatArray) }, { programId, location -> val buffer = FloatArray(4 * 4) glGetUniformfv(programId, location, buffer) Matrix4f().set(buffer) }) fun enable() { glUseProgram(programId) for ((_, queued) in queuedUniformValues) { queued() } for ((index, texture) in textures) { glActiveTexture(GL_TEXTURE0 + index) glBindTexture(GL_TEXTURE_2D, texture) } queuedUniformValues.clear() } fun delete() { glDeleteProgram(programId) } fun disable() { glUseProgram(0) } fun uses(name: String): Boolean { return getUniformLocation(name) != -1 } fun camera(renderer: ICamera<*>) { this.projection = renderer.projection.toFloat() this.view = renderer.view.toFloat() } constructor(vertexShader: Shader, fragmentShader: Shader) : this() { glAttachShader(programId, vertexShader.shaderId) glAttachShader(programId, fragmentShader.shaderId) glLinkProgram(programId) val success = stack.mallocInt(1) glGetProgramiv(programId, GL_LINK_STATUS, success) if (success.get(0) == 0) { throw ShaderProgramLinkingException(glGetProgramInfoLog(programId)) } } }