commonMain.com.soywiz.korag.OpenglAG.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of korgw Show documentation
Show all versions of korgw Show documentation
Portable UI with accelerated graphics support for Kotlin
package com.soywiz.korag
import com.soywiz.kds.Extra
import com.soywiz.kds.FastStringMap
import com.soywiz.kds.getOrPut
import com.soywiz.kds.iterators.*
import com.soywiz.kgl.*
import com.soywiz.klock.*
import com.soywiz.klock.min
import com.soywiz.klogger.*
import com.soywiz.kmem.*
import com.soywiz.korag.internal.setFloats
import com.soywiz.korag.shader.Program
import com.soywiz.korag.shader.ProgramConfig
import com.soywiz.korag.shader.VarKind
import com.soywiz.korag.shader.VarType
import com.soywiz.korag.shader.gl.*
import com.soywiz.korim.bitmap.*
import com.soywiz.korim.color.RGBA
import com.soywiz.korim.vector.BitmapVector
import com.soywiz.korio.lang.*
import com.soywiz.korma.geom.*
import com.soywiz.krypto.encoding.*
import kotlin.jvm.JvmOverloads
import kotlin.math.*
abstract class AGOpengl : AG() {
class ShaderException(val str: String, val error: String, val errorInt: Int, val gl: KmlGl) :
RuntimeException("Error Compiling Shader : $errorInt : '$error' : source='$str', gl.versionInt=${gl.versionInt}, gl.versionString='${gl.versionString}', gl=$gl")
open var isGlAvailable = true
abstract val gl: KmlGl
override val graphicExtensions: Set get() = gl.graphicExtensions
override val isFloatTextureSupported: Boolean get() = gl.isFloatTextureSupported
override val isInstancedSupported: Boolean get() = gl.isInstancedSupported
open val glSlVersion: Int? = null
open val gles: Boolean = false
open val linux: Boolean = false
open val android: Boolean = false
open val webgl: Boolean get() = false
open val webgl2: Boolean get() = false
override var devicePixelRatio: Double = 1.0
override fun contextLost() {
Console.info("AG.contextLost()", this, gl, gl.root)
contextVersion++
}
//val queue = Deque<(gl: GL) -> Unit>()
override fun createBuffer(kind: Buffer.Kind): Buffer = GlBuffer(kind)
open fun setSwapInterval(value: Int) {
//gl.swapInterval = 0
}
private fun setViewport(buffer: BaseRenderBuffer) {
gl.viewport(buffer.x, buffer.y, buffer.width, buffer.height)
//println("setViewport: ${buffer.x}, ${buffer.y}, ${buffer.width}, ${buffer.height}")
}
override fun createMainRenderBuffer(): BaseRenderBufferImpl {
var backBufferTextureBinding2d: Int = 0
var backBufferRenderBufferBinding: Int = 0
var backBufferFrameBufferBinding: Int = 0
return object : BaseRenderBufferImpl() {
override fun init() {
backBufferTextureBinding2d = gl.getIntegerv(gl.TEXTURE_BINDING_2D)
backBufferRenderBufferBinding = gl.getIntegerv(gl.RENDERBUFFER_BINDING)
backBufferFrameBufferBinding = gl.getIntegerv(gl.FRAMEBUFFER_BINDING)
}
override fun set() {
setViewport(this)
gl.bindTexture(gl.TEXTURE_2D, backBufferTextureBinding2d)
gl.bindRenderbuffer(gl.RENDERBUFFER, backBufferRenderBufferBinding)
gl.bindFramebuffer(gl.FRAMEBUFFER, backBufferFrameBufferBinding)
}
override fun unset() {
backBufferTextureBinding2d = gl.getIntegerv(gl.TEXTURE_BINDING_2D)
backBufferRenderBufferBinding = gl.getIntegerv(gl.RENDERBUFFER_BINDING)
backBufferFrameBufferBinding = gl.getIntegerv(gl.FRAMEBUFFER_BINDING)
}
}
}
fun createGlState() = KmlGlState(gl)
var lastRenderContextId = 0
inner class GlRenderBuffer : RenderBuffer() {
var cachedVersion = -1
override val id = lastRenderContextId++
val ftex get() = tex as GlTexture
val depth = FBuffer(4)
val framebuffer = FBuffer(4)
override fun set() {
setViewport(this)
//val width = this.width.nextPowerOfTwo
//val height = this.height.nextPowerOfTwo
if (dirty) {
dirty = false
setSwapInterval(0)
if (cachedVersion != contextVersion) {
cachedVersion = contextVersion
gl.genRenderbuffers(1, depth)
gl.genFramebuffers(1, framebuffer)
}
gl.bindTexture(gl.TEXTURE_2D, ftex.tex)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)
gl.bindTexture(gl.TEXTURE_2D, 0)
gl.bindRenderbuffer(gl.RENDERBUFFER, depth.getInt(0))
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height)
}
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer.getInt(0))
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, ftex.tex, 0)
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth.getInt(0))
}
override fun close() {
gl.apply {
deleteFramebuffers(1, framebuffer)
deleteRenderbuffers(1, depth)
framebuffer.setInt(0, 0)
depth.setInt(0, 0)
}
}
override fun toString(): String = "GlRenderBuffer[$id]($width, $height)"
}
override fun createRenderBuffer(): RenderBuffer = GlRenderBuffer()
private fun BlendEquation.toGl() = when (this) {
BlendEquation.ADD -> gl.FUNC_ADD
BlendEquation.SUBTRACT -> gl.FUNC_SUBTRACT
BlendEquation.REVERSE_SUBTRACT -> gl.FUNC_REVERSE_SUBTRACT
}
private fun BlendFactor.toGl() = when (this) {
BlendFactor.DESTINATION_ALPHA -> gl.DST_ALPHA
BlendFactor.DESTINATION_COLOR -> gl.DST_COLOR
BlendFactor.ONE -> gl.ONE
BlendFactor.ONE_MINUS_DESTINATION_ALPHA -> gl.ONE_MINUS_DST_ALPHA
BlendFactor.ONE_MINUS_DESTINATION_COLOR -> gl.ONE_MINUS_DST_COLOR
BlendFactor.ONE_MINUS_SOURCE_ALPHA -> gl.ONE_MINUS_SRC_ALPHA
BlendFactor.ONE_MINUS_SOURCE_COLOR -> gl.ONE_MINUS_SRC_COLOR
BlendFactor.SOURCE_ALPHA -> gl.SRC_ALPHA
BlendFactor.SOURCE_COLOR -> gl.SRC_COLOR
BlendFactor.ZERO -> gl.ZERO
}
fun TriangleFace.toGl() = when (this) {
TriangleFace.FRONT -> gl.FRONT
TriangleFace.BACK -> gl.BACK
TriangleFace.FRONT_AND_BACK -> gl.FRONT_AND_BACK
TriangleFace.NONE -> gl.FRONT
}
fun CompareMode.toGl() = when (this) {
CompareMode.ALWAYS -> gl.ALWAYS
CompareMode.EQUAL -> gl.EQUAL
CompareMode.GREATER -> gl.GREATER
CompareMode.GREATER_EQUAL -> gl.GEQUAL
CompareMode.LESS -> gl.LESS
CompareMode.LESS_EQUAL -> gl.LEQUAL
CompareMode.NEVER -> gl.NEVER
CompareMode.NOT_EQUAL -> gl.NOTEQUAL
}
fun StencilOp.toGl() = when (this) {
StencilOp.DECREMENT_SATURATE -> gl.DECR
StencilOp.DECREMENT_WRAP -> gl.DECR_WRAP
StencilOp.INCREMENT_SATURATE -> gl.INCR
StencilOp.INCREMENT_WRAP -> gl.INCR_WRAP
StencilOp.INVERT -> gl.INVERT
StencilOp.KEEP -> gl.KEEP
StencilOp.SET -> gl.REPLACE
StencilOp.ZERO -> gl.ZERO
}
private val TEMP_MAX_MATRICES = 1024
val tempBuffer1 = FBuffer(4)
val tempBuffer = FBuffer(4 * 16 * TEMP_MAX_MATRICES)
val tempBufferM2 = FBuffer.allocUnaligned(4 * 2 * 2)
val tempBufferM3 = FBuffer.allocUnaligned(4 * 3 * 3)
val tempBufferM4 = FBuffer.allocUnaligned(4 * 4 * 4)
val tempF32 = tempBuffer.f32
private val tempFloats = FloatArray(16 * TEMP_MAX_MATRICES)
private val mat3dArray = arrayOf(Matrix3D())
private val finalScissorBL = Rectangle()
private val tempRect = Rectangle()
fun applyScissorState(scissor: AG.Scissor? = null) {
//println("applyScissorState")
if (this.currentRenderBuffer == null) {
//println("this.currentRenderBuffer == null")
}
val currentRenderBuffer = this.currentRenderBuffer ?: return
if (currentRenderBuffer === mainRenderBuffer) {
var realScissors: Rectangle? = finalScissorBL
realScissors?.setTo(0.0, 0.0, realBackWidth.toDouble(), realBackHeight.toDouble())
if (scissor != null) {
tempRect.setTo(currentRenderBuffer.x + scissor.x, ((currentRenderBuffer.y + currentRenderBuffer.height) - (scissor.y + scissor.height)), (scissor.width), scissor.height)
realScissors = realScissors?.intersection(tempRect, realScissors)
}
//println("currentRenderBuffer: $currentRenderBuffer")
val renderBufferScissor = currentRenderBuffer.scissor
if (renderBufferScissor != null) {
realScissors = realScissors?.intersection(renderBufferScissor.rect, realScissors)
}
//println("finalScissorBL: $finalScissorBL, renderBufferScissor: $renderBufferScissor")
gl.enable(gl.SCISSOR_TEST)
if (realScissors != null) {
gl.scissor(realScissors.x.toInt(), realScissors.y.toInt(), realScissors.width.toInt(), realScissors.height.toInt())
} else {
gl.scissor(0, 0, 0, 0)
}
} else {
gl.enableDisable(gl.SCISSOR_TEST, scissor != null)
if (scissor != null) {
gl.scissor(scissor.x, scissor.y, scissor.width, scissor.height)
}
}
}
override fun draw(batch: Batch) {
val instances = batch.instances
val program = batch.program
val type = batch.type
val vertexCount = batch.vertexCount
val indices = batch.indices
val indexType = batch.indexType
val offset = batch.offset
val blending = batch.blending
val uniforms = batch.uniforms
val stencil = batch.stencil
val colorMask = batch.colorMask
val renderState = batch.renderState
val scissor = batch.scissor
//finalScissor.setTo(0, 0, backWidth, backHeight)
applyScissorState(scissor)
var useExternalSampler = false
for (n in 0 until uniforms.uniforms.size) {
val uniform = uniforms.uniforms[n]
val uniformType = uniform.type
val value = uniforms.values[n]
when (uniformType) {
VarType.TextureUnit -> {
val unit = value as TextureUnit
val tex = (unit.texture as GlTexture?)
if (tex != null) {
if (tex.forcedTexTarget != gl.TEXTURE_2D && tex.forcedTexTarget != -1) {
useExternalSampler = true
}
}
}
}
}
if (indices != null && indices.kind != Buffer.Kind.INDEX) invalidOp("Not a IndexBuffer")
val programConfig = if (useExternalSampler) ProgramConfig.EXTERNAL_TEXTURE_SAMPLER else ProgramConfig.DEFAULT
val glProgram = getProgram(program, programConfig)
(indices as? GlBuffer?)?.bind(gl)
glProgram.use()
batch.vertexData.fastForEach { entry ->
val vertices = entry.buffer as GlBuffer
val vertexLayout = entry.layout
val vattrs = vertexLayout.attributes
val vattrspos = vertexLayout.attributePositions
if (vertices.kind != AG.Buffer.Kind.VERTEX) invalidOp("Not a VertexBuffer")
vertices.bind(gl)
val totalSize = vertexLayout.totalSize
for (n in 0 until vattrspos.size) {
val att = vattrs[n]
if (!att.active) continue
val off = vattrspos[n]
val loc = glProgram.getAttribLocation(att.name)
val glElementType = att.type.glElementType
val elementCount = att.type.elementCount
if (loc >= 0) {
gl.enableVertexAttribArray(loc)
gl.vertexAttribPointer(loc, elementCount, glElementType, att.normalized, totalSize, off.toLong())
if (att.divisor != 0) {
gl.vertexAttribDivisor(loc, att.divisor)
}
}
}
}
var textureUnit = 0
//for ((uniform, value) in uniforms) {
for (n in 0 until uniforms.uniforms.size) {
val uniform = uniforms.uniforms[n]
val uniformName = uniform.name
val uniformType = uniform.type
val value = uniforms.values[n]
val location = gl.getUniformLocation(glProgram.id, uniformName)
val declArrayCount = uniform.arrayCount
val stride = uniform.type.elementCount
//println("uniform: $uniform, arrayCount=$arrayCount, stride=$stride")
when (uniformType) {
VarType.TextureUnit -> {
val unit = value as TextureUnit
gl.activeTexture(gl.TEXTURE0 + textureUnit)
val tex = (unit.texture as GlTexture?)
tex?.bindEnsuring()
tex?.setFilter(unit.linear)
gl.uniform1i(location, textureUnit)
textureUnit++
}
VarType.SamplerCube -> {
val unit = value as TextureUnit
gl.activeTexture(gl.TEXTURE0 + textureUnit)
val tex = unit.texture as TextureGeneric
tex.initialiseIfNeeded()
tex.bindEnsuring()
gl.uniform1i(location, textureUnit)
textureUnit++
}
VarType.Mat2, VarType.Mat3, VarType.Mat4 -> {
val matArray = when (value) {
is Array<*> -> value
is Matrix3D -> mat3dArray.also { it[0].copyFrom(value) }
else -> error("Not an array or a matrix3d")
} as Array
val arrayCount = min(declArrayCount, matArray.size)
val matSize = when (uniformType) {
VarType.Mat2 -> 2; VarType.Mat3 -> 3; VarType.Mat4 -> 4; else -> -1
}
for (n in 0 until arrayCount) {
matArray[n].copyToFloatWxH(tempFloats, matSize, matSize, MajorOrder.COLUMN, n * stride)
}
tempBuffer.setFloats(0, tempFloats, 0, stride * arrayCount)
if (webgl) {
//if (true) {
val tb = when (uniformType) {
VarType.Mat2 -> tempBufferM2
VarType.Mat3 -> tempBufferM3
VarType.Mat4 -> tempBufferM4
else -> tempBufferM4
}
for (n in 0 until arrayCount) {
val itLocation = if (arrayCount == 1) location else gl.getUniformLocation(glProgram.id, uniform.indexNames[n])
arraycopy(tempBuffer.f32, n * stride, tb.f32, 0, stride)
//println("[WEBGL] uniformName[$uniformName]=$itLocation, tb: ${tb.f32.size}")
when (uniform.type) {
VarType.Mat2 -> gl.uniformMatrix2fv(itLocation, 1, false, tb)
VarType.Mat3 -> gl.uniformMatrix3fv(itLocation, 1, false, tb)
VarType.Mat4 -> gl.uniformMatrix4fv(itLocation, 1, false, tb)
else -> invalidOp("Don't know how to set uniform matrix ${uniform.type}")
}
}
} else {
//println("[NO-WEBGL] uniformName[$uniformName]=$location ")
when (uniform.type) {
VarType.Mat2 -> gl.uniformMatrix2fv(location, arrayCount, false, tempBuffer)
VarType.Mat3 -> gl.uniformMatrix3fv(location, arrayCount, false, tempBuffer)
VarType.Mat4 -> gl.uniformMatrix4fv(location, arrayCount, false, tempBuffer)
else -> invalidOp("Don't know how to set uniform matrix ${uniform.type}")
}
}
}
VarType.Float1, VarType.Float2, VarType.Float3, VarType.Float4 -> {
var arrayCount = declArrayCount
when (value) {
is Number -> tempBuffer.setAlignedFloat32(0, value.toFloat())
is Vector3D -> tempBuffer.setFloats(0, value.data, 0, stride)
is FloatArray -> {
arrayCount = min(declArrayCount, value.size / stride)
tempBuffer.setFloats(0, value, 0, stride * arrayCount)
}
is Array<*> -> {
arrayCount = min(declArrayCount, value.size)
for (n in 0 until value.size) {
val vector = value[n] as Vector3D
tempBuffer.setFloats(n * stride, vector.data, 0, stride)
}
}
else -> error("Unknown type '$value'")
}
//if (true) {
if (webgl) {
val tb = tempBufferM2
for (n in 0 until arrayCount) {
val itLocation = if (arrayCount == 1) location else gl.getUniformLocation(glProgram.id, uniform.indexNames[n])
val f32 = tb.f32
//println("uniformName[$uniformName] = $itLocation")
arraycopy(tempBuffer.f32, 0, tb.f32, 0, stride)
when (uniform.type) {
VarType.Float1 -> gl.uniform1f(itLocation, f32[0])
VarType.Float2 -> gl.uniform2f(itLocation, f32[0], f32[1])
VarType.Float3 -> gl.uniform3f(itLocation, f32[0], f32[1], f32[2])
VarType.Float4 -> gl.uniform4f(itLocation, f32[0], f32[1], f32[2], f32[3])
else -> Unit
}
}
} else {
when (uniform.type) {
VarType.Float1 -> gl.uniform1fv(location, arrayCount, tempBuffer)
VarType.Float2 -> gl.uniform2fv(location, arrayCount, tempBuffer)
VarType.Float3 -> gl.uniform3fv(location, arrayCount, tempBuffer)
VarType.Float4 -> gl.uniform4fv(location, arrayCount, tempBuffer)
else -> Unit
}
}
}
else -> invalidOp("Don't know how to set uniform ${uniform.type}")
}
}
if (blending.enabled) {
gl.enable(gl.BLEND)
gl.blendEquationSeparate(blending.eqRGB.toGl(), blending.eqA.toGl())
gl.blendFuncSeparate(
blending.srcRGB.toGl(), blending.dstRGB.toGl(),
blending.srcA.toGl(), blending.dstA.toGl()
)
} else {
gl.disable(gl.BLEND)
}
if (renderState.frontFace == FrontFace.BOTH) {
gl.disable(gl.CULL_FACE)
} else {
gl.enable(gl.CULL_FACE)
gl.frontFace(if (renderState.frontFace == FrontFace.CW) gl.CW else gl.CCW)
}
gl.depthMask(renderState.depthMask)
gl.depthRangef(renderState.depthNear, renderState.depthFar)
gl.lineWidth(renderState.lineWidth)
if (renderState.depthFunc != CompareMode.ALWAYS) {
gl.enable(gl.DEPTH_TEST)
gl.depthFunc(renderState.depthFunc.toGl())
} else {
gl.disable(gl.DEPTH_TEST)
}
gl.colorMask(colorMask.red, colorMask.green, colorMask.blue, colorMask.alpha)
if (stencil.enabled) {
gl.enable(gl.STENCIL_TEST)
gl.stencilFunc(stencil.compareMode.toGl(), stencil.referenceValue, stencil.readMask)
gl.stencilOp(
stencil.actionOnDepthFail.toGl(),
stencil.actionOnDepthPassStencilFail.toGl(),
stencil.actionOnBothPass.toGl()
)
gl.stencilMask(stencil.writeMask)
} else {
gl.disable(gl.STENCIL_TEST)
gl.stencilMask(0)
}
//val viewport = FBuffer(4 * 4)
//gl.getIntegerv(gl.VIEWPORT, viewport)
//println("viewport=${viewport.getAlignedInt32(0)},${viewport.getAlignedInt32(1)},${viewport.getAlignedInt32(2)},${viewport.getAlignedInt32(3)}")
if (indices != null) {
if (instances != 1) {
gl.drawElementsInstanced(type.glDrawMode, vertexCount, indexType.glIndexType, offset, instances)
} else {
gl.drawElements(type.glDrawMode, vertexCount, indexType.glIndexType, offset)
}
} else {
if (instances != 1) {
gl.drawArraysInstanced(type.glDrawMode, offset, vertexCount, instances)
} else {
gl.drawArrays(type.glDrawMode, offset, vertexCount)
}
}
//glSetActiveTexture(gl.TEXTURE0)
batch.vertexData.fastForEach { entry ->
val vattrs = entry.layout.attributes
vattrs.fastForEach { att ->
if (att.active) {
val loc = glProgram.getAttribLocation(att.name).toInt()
if (loc >= 0) {
if (att.divisor != 0) {
gl.vertexAttribDivisor(loc, 0)
}
gl.disableVertexAttribArray(loc)
}
}
}
}
}
val TextureTargetKind.glTarget: Int
get() = when (this) {
TextureTargetKind.TEXTURE_2D -> gl.TEXTURE_2D
TextureTargetKind.TEXTURE_3D -> gl.TEXTURE_3D
TextureTargetKind.TEXTURE_CUBE_MAP -> gl.TEXTURE_CUBE_MAP
}
val DrawType.glDrawMode: Int
get() = when (this) {
DrawType.POINTS -> gl.POINTS
DrawType.LINE_STRIP -> gl.LINE_STRIP
DrawType.LINE_LOOP -> gl.LINE_LOOP
DrawType.LINES -> gl.LINES
DrawType.TRIANGLE_STRIP -> gl.TRIANGLE_STRIP
DrawType.TRIANGLE_FAN -> gl.TRIANGLE_FAN
DrawType.TRIANGLES -> gl.TRIANGLES
}
val VarType.glElementType: Int
get() = when (this.kind) {
VarKind.TBYTE -> gl.BYTE
VarKind.TUNSIGNED_BYTE -> gl.UNSIGNED_BYTE
VarKind.TSHORT -> gl.SHORT
VarKind.TUNSIGNED_SHORT -> gl.UNSIGNED_SHORT
VarKind.TINT -> gl.UNSIGNED_INT
VarKind.TFLOAT -> gl.FLOAT
}
val IndexType.glIndexType: Int
get() = when (this) {
IndexType.UBYTE -> gl.UNSIGNED_BYTE
IndexType.USHORT -> gl.UNSIGNED_SHORT
IndexType.UINT -> gl.UNSIGNED_INT
}
private val programs = HashMap>()
@JvmOverloads
fun getProgram(program: Program, config: ProgramConfig = ProgramConfig.DEFAULT): GlProgram {
return programs.getOrPut(program) { HashMap() }.getOrPut(config) { GlProgram(gl, program, config) }
}
inner class GlProgram(val gl: KmlGl, val program: Program, val programConfig: ProgramConfig) : Closeable {
var cachedVersion = -1
var id: Int = 0
var fragmentShaderId: Int = 0
var vertexShaderId: Int = 0
val cachedAttribLocations = FastStringMap()
fun getAttribLocation(name: String): Int {
return cachedAttribLocations.getOrPut(name) {
gl.getAttribLocation(id, name)
}
}
private fun String.replaceVersion(version: Int) = this.replace("#version 100", "#version $version")
private inline fun createShaderCompat(type: Int, gen: (compat: Boolean) -> String): Int {
return try {
createShader(type, gen(true))
} catch (e: ShaderException) {
createShader(type, gen(false))
}
}
private fun ensure() {
if (cachedVersion != contextVersion) {
val time = measureTime {
val oldCachedVersion = cachedVersion
cachedVersion = contextVersion
id = gl.createProgram()
if (GlslGenerator.DEBUG_GLSL) {
Console.warn("OpenglAG: Creating program ${program.name} with id $id because contextVersion: $oldCachedVersion != $contextVersion")
}
//println("GL_SHADING_LANGUAGE_VERSION: $glslVersionInt : $glslVersionString")
val guessedGlSlVersion = glSlVersion ?: gl.versionInt
val usedGlSlVersion = GlslGenerator.FORCE_GLSL_VERSION?.toIntOrNull() ?: when (guessedGlSlVersion) {
460 -> 460
in 300..450 -> 100
else -> guessedGlSlVersion
}
if (GlslGenerator.DEBUG_GLSL) {
Console.trace("GLSL version: requested=$glSlVersion, guessed=$guessedGlSlVersion, forced=${GlslGenerator.FORCE_GLSL_VERSION}. used=$usedGlSlVersion")
}
fragmentShaderId = createShaderCompat(gl.FRAGMENT_SHADER) { compatibility ->
program.fragment.toNewGlslStringResult(GlslConfig(gles = gles, version = usedGlSlVersion, compatibility = compatibility, android = android, programConfig = programConfig)).result
}
vertexShaderId = createShaderCompat(gl.VERTEX_SHADER) { compatibility ->
program.vertex.toNewGlslStringResult(GlslConfig(gles = gles, version = usedGlSlVersion, compatibility = compatibility, android = android, programConfig = programConfig)).result
}
gl.attachShader(id, fragmentShaderId)
gl.attachShader(id, vertexShaderId)
gl.linkProgram(id)
tempBuffer1.setInt(0, 0)
gl.getProgramiv(id, gl.LINK_STATUS, tempBuffer1)
}
if (GlslGenerator.DEBUG_GLSL) {
Console.info("OpenglAG: Created program ${program.name} with id $id in time=$time")
}
}
}
private fun createShader(type: Int, str: String): Int {
val shaderId = gl.createShader(type)
gl.shaderSource(shaderId, str)
gl.compileShader(shaderId)
val out = gl.getShaderiv(shaderId, gl.COMPILE_STATUS)
val errorInt = gl.getError()
if (out != gl.GTRUE) {
val error = gl.getShaderInfoLog(shaderId)
throw ShaderException(str, error, errorInt, gl)
}
return shaderId
}
fun use() {
ensure()
gl.useProgram(id)
}
fun unuse() {
ensure()
gl.useProgram(0)
}
override fun close() {
gl.deleteShader(fragmentShaderId)
gl.deleteShader(vertexShaderId)
gl.deleteProgram(id)
}
}
// @TODO: Kotlin inline bug
//Back-end (JVM) Internal error: wrong code generated
//org.jetbrains.kotlin.codegen.CompilationException Back-end (JVM) Internal error: Couldn't transform method node:
//clear (IFIZZZ)V:
override fun clear(
color: RGBA,
depth: Float,
stencil: Int,
clearColor: Boolean,
clearDepth: Boolean,
clearStencil: Boolean
) {
//println("CLEAR: $color, $depth")
var bits = 0
applyScissorState(null)
//gl.disable(gl.SCISSOR_TEST)
if (clearColor) {
bits = bits or gl.COLOR_BUFFER_BIT
gl.clearColor(color.rf, color.gf, color.bf, color.af)
}
if (clearDepth) {
bits = bits or gl.DEPTH_BUFFER_BIT
gl.clearDepthf(depth)
}
if (clearStencil) {
bits = bits or gl.STENCIL_BUFFER_BIT
gl.stencilMask(-1)
gl.clearStencil(stencil)
}
gl.clear(bits)
}
override fun createTexture(premultiplied: Boolean): Texture = GlTexture(this.gl, premultiplied)
override fun createTexture(targetKind: TextureTargetKind, init: Texture.(gl: KmlGl) -> Unit): Texture {
val tex = TextureGeneric(targetKind, init)
return tex
}
inner class TextureGeneric(val targetKind: TextureTargetKind, val init: Texture.(gl: KmlGl) -> Unit) : Texture() {
private var initialised: Boolean = false
val texRef: Int by lazy {
val texIds = FBuffer(4)
gl.genTextures(1, texIds)
texIds.getInt(0)
}
fun initialiseIfNeeded() {
if (!initialised) {
this.texRef
this.init(gl)
this.initialised = true
}
}
override fun bind() {
gl.bindTexture(targetKind.glTarget, this.texRef)
}
override fun actualSyncUpload(source: BitmapSourceBase, bmp: Bitmap?, requestMipmaps: Boolean) {
this.mipmaps = false
val bytesPerPixel = if (source.rgba) 4 else 1
val type = if (source.rgba) {
//if (source is NativeImage) gl.BGRA else gl.RGBA
gl.RGBA
} else {
gl.LUMINANCE
}
val bmp = when (bmp) {
is BitmapVector -> bmp.nativeImage
else -> bmp
}
when (bmp) {
is NativeImage -> {
prepareUploadNativeTexture(bmp)
if (bmp.area != 0) {
init.invoke(this, gl)
}
}
else -> {
}
}
}
}
inner class GlBuffer(kind: Buffer.Kind) : Buffer(kind) {
var cachedVersion = -1
private var id = -1
val glKind = if (kind == Buffer.Kind.INDEX) gl.ELEMENT_ARRAY_BUFFER else gl.ARRAY_BUFFER
override fun afterSetMem() {
}
override fun close() {
fbuffer(4) { buffer ->
buffer.setInt(0, this.id)
gl.deleteBuffers(1, buffer)
}
id = -1
}
fun getGlId(gl: KmlGl): Int {
if (cachedVersion != contextVersion) {
cachedVersion = contextVersion
dirty = true
id = -1
}
if (id < 0) {
id = fbuffer(4) {
gl.genBuffers(1, it)
it.getInt(0)
}
}
if (dirty) {
_bind(gl, id)
if (mem != null) {
gl.bufferData(glKind, memLength, mem!!, gl.STATIC_DRAW)
}
}
return id
}
fun _bind(gl: KmlGl, id: Int) {
gl.bindBuffer(glKind, id)
}
fun bind(gl: KmlGl) {
_bind(gl, getGlId(gl))
}
}
open fun prepareUploadNativeTexture(bmp: NativeImage) {
}
inner class GlTexture(
val gl: KmlGl,
override val premultiplied: Boolean
) : Texture() {
var cachedVersion = -1
val texIds = FBuffer(4)
var forcedTexId: Int = -1
var forcedTexTarget: Int = gl.TEXTURE_2D
val tex: Int
get() {
if (forcedTexId >= 0) return forcedTexId
if (cachedVersion != contextVersion) {
cachedVersion = contextVersion
invalidate()
gl.genTextures(1, texIds)
}
return texIds.getInt(0)
}
fun createBufferForBitmap(bmp: Bitmap?): FBuffer? = when (bmp) {
null -> null
is NativeImage -> unsupported("Should not call createBufferForBitmap with a NativeImage")
is Bitmap8 -> FBuffer(bmp.area).also { mem -> arraycopy(bmp.data, 0, mem.arrayByte, 0, bmp.area) }
is FloatBitmap32 -> FBuffer(bmp.area * 4 * 4).also { mem -> arraycopy(bmp.data, 0, mem.arrayFloat, 0, bmp.area * 4) }
else -> FBuffer(bmp.area * 4).also { mem ->
val abmp: Bitmap32 = if (premultiplied) bmp.toBMP32IfRequired().premultipliedIfRequired() else bmp.toBMP32IfRequired().depremultipliedIfRequired()
arraycopy(abmp.data.ints, 0, mem.arrayInt, 0, abmp.area)
}
}
override fun actualSyncUpload(source: BitmapSourceBase, bmp: Bitmap?, requestMipmaps: Boolean) {
this.mipmaps = false
val bytesPerPixel = if (source.rgba) 4 else 1
val type = if (source.rgba) {
//if (source is NativeImage) gl.BGRA else gl.RGBA
gl.RGBA
} else {
gl.LUMINANCE
}
val isFloat = bmp is FloatBitmap32
val bmp = when (bmp) {
is BitmapVector -> bmp.nativeImage
else -> bmp
}
when (bmp) {
is ForcedTexId -> {
this.forcedTexId = bmp.forcedTexId
if (bmp is ForcedTexTarget) this.forcedTexTarget = bmp.forcedTexTarget
return
}
is NativeImage -> {
if (bmp.forcedTexId != -1) {
this.forcedTexId = bmp.forcedTexId
if (bmp.forcedTexTarget != -1) this.forcedTexTarget = bmp.forcedTexTarget
return
}
prepareUploadNativeTexture(bmp)
if (bmp.area != 0) {
prepareTexImage2D()
gl.texImage2D(forcedTexTarget, 0, type, type, gl.UNSIGNED_BYTE, bmp)
}
}
else -> {
val buffer = createBufferForBitmap(bmp)
if (buffer != null && source.width != 0 && source.height != 0 && buffer.size != 0) {
prepareTexImage2D()
val internalFormat = when {
isFloat && (webgl2 || !webgl) -> GL_RGBA32F
else -> type
}
val format = type
val texType = when {
isFloat -> gl.FLOAT
else -> gl.UNSIGNED_BYTE
}
//println("actualSyncUpload: webgl=$webgl, internalFormat=${internalFormat.hex}, format=${format.hex}, textype=${texType.hex}")
gl.texImage2D(forcedTexTarget, 0, internalFormat, source.width, source.height, 0, format, texType, buffer)
}
//println(buffer)
}
}
if (requestMipmaps && source.width.isPowerOfTwo && source.height.isPowerOfTwo) {
//println(" - mipmaps")
this.mipmaps = true
bind()
setFilter(true)
setWrapST()
//println("actualSyncUpload,generateMipmap.SOURCE: ${source.width},${source.height}, source=$source, bmp=$bmp, requestMipmaps=$requestMipmaps")
//printStackTrace()
gl.generateMipmap(forcedTexTarget)
} else {
//println(" - nomipmaps")
}
}
private val GL_RGBA32F = 0x8814
// https://download.blender.org/source/chest/blender_1.72_tree/glut-win/glut_bitmap.c
private val GL_UNPACK_ALIGNMENT = 0x0CF5
private val GL_UNPACK_LSB_FIRST = 0x0CF1
private val GL_UNPACK_ROW_LENGTH = 0x0CF2
private val GL_UNPACK_SKIP_PIXELS = 0x0CF4
private val GL_UNPACK_SKIP_ROWS = 0x0CF3
private val GL_UNPACK_SWAP_BYTES = 0x0CF0
fun prepareTexImage2D() {
if (linux) {
//println("prepareTexImage2D")
//gl.pixelStorei(GL_UNPACK_LSB_FIRST, gl.TRUE)
gl.pixelStorei(GL_UNPACK_LSB_FIRST, gl.GFALSE)
gl.pixelStorei(GL_UNPACK_SWAP_BYTES, gl.GTRUE)
}
}
override fun bind(): Unit = gl.bindTexture(forcedTexTarget, tex)
override fun unbind(): Unit = gl.bindTexture(forcedTexTarget, 0)
private var closed = false
override fun close(): Unit {
super.close()
if (!closed) {
closed = true
if (cachedVersion == contextVersion) {
gl.deleteTextures(1, texIds)
//println("DELETE texture: ${texIds[0]}")
texIds[0] = -1
} else {
//println("YAY! NO DELETE texture because in new context and would remove the wrong texture: ${texIds[0]}")
}
}
}
fun setFilter(linear: Boolean) {
val minFilter = if (this.mipmaps) {
if (linear) gl.LINEAR_MIPMAP_NEAREST else gl.NEAREST_MIPMAP_NEAREST
} else {
if (linear) gl.LINEAR else gl.NEAREST
}
val magFilter = if (linear) gl.LINEAR else gl.NEAREST
setWrapST()
setMinMag(minFilter, magFilter)
}
private fun setWrapST() {
gl.texParameteri(forcedTexTarget, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(forcedTexTarget, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
}
private fun setMinMag(min: Int, mag: Int) {
gl.texParameteri(forcedTexTarget, gl.TEXTURE_MIN_FILTER, min)
gl.texParameteri(forcedTexTarget, gl.TEXTURE_MAG_FILTER, mag)
}
}
override fun readColor(bitmap: Bitmap32) {
fbuffer(bitmap.area * 4) { buffer ->
gl.readPixels(
0, 0, bitmap.width, bitmap.height,
gl.RGBA, gl.UNSIGNED_BYTE, buffer
)
buffer.getAlignedArrayInt32(0, bitmap.data.ints, 0, bitmap.area)
//println("readColor.HASH:" + bitmap.computeHash())
}
}
override fun readDepth(width: Int, height: Int, out: FloatArray) {
val area = width * height
fbuffer(area * 4) { buffer ->
gl.readPixels(
0, 0, width, height, gl.DEPTH_COMPONENT, gl.FLOAT,
buffer
)
buffer.getAlignedArrayFloat32(0, out, 0, area)
}
}
override fun readColorTexture(texture: Texture, width: Int, height: Int) {
gl.apply {
texture.bind()
copyTexImage2D(TEXTURE_2D, 0, RGBA, 0, 0, width, height, 0)
texture.unbind()
}
}
}
val KmlGl.versionString by Extra.PropertyThis {
getString(SHADING_LANGUAGE_VERSION)
}
val KmlGl.versionInt by Extra.PropertyThis {
versionString.replace(".", "").trim().toIntOrNull() ?: 100
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy