All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.earth.worldwind.render.Texture.kt Maven / Gradle / Ivy

package earth.worldwind.render

import earth.worldwind.draw.DrawContext
import earth.worldwind.geom.Matrix3
import earth.worldwind.util.Logger.ERROR
import earth.worldwind.util.Logger.logMessage
import earth.worldwind.util.kgl.*
import earth.worldwind.util.math.powerOfTwoCeiling

open class Texture(val width: Int, val height: Int, protected val format: Int, protected val type: Int, protected val isRT: Boolean = false) : RenderResource {
    companion object {
        protected fun estimateByteCount(width: Int, height: Int, format: Int, type: Int, hasMipMap: Boolean): Int {
            require(width >= 0 && height >= 0) {
                logMessage(ERROR, "Texture", "estimateByteCount", "invalidWidthOrHeight")
            }
            // Compute the number of bytes per row of texture image level 0. Use a default of 32 bits per pixel when either
            // of the bitmap's type or internal format are unrecognized. Adjust the width to the next highest power-of-two
            // to better estimate the memory consumed by non-power-of-two images.
            val widthPow2 = powerOfTwoCeiling(width)
            val bytesPerRow = when (type) {
                GL_UNSIGNED_BYTE -> when (format) {
                    GL_ALPHA, GL_LUMINANCE -> widthPow2 // 8 bits per pixel
                    GL_LUMINANCE_ALPHA -> widthPow2 * 2 // 16 bits per pixel
                    GL_RGB -> widthPow2 * 3 // 24 bits per pixel
                    GL_RGBA -> widthPow2 * 4 // 32 bits per pixel
                    else -> widthPow2 * 4 // 32 bits per pixel
                }
                GL_UNSIGNED_SHORT, GL_UNSIGNED_SHORT_5_6_5,
                GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1 -> widthPow2 * 2 // 16 bits per pixel
                GL_UNSIGNED_INT -> widthPow2 * 4 // 32 bits per pixel
                else -> widthPow2 * 4 // 32 bits per pixel
            }

            // Compute the number of bytes for the entire texture image level 0 (i.e. bytePerRow * numRows). Adjust the
            // height to the next highest power-of-two to better estimate the memory consumed by non power-of-two images.
            val heightPow2 = powerOfTwoCeiling(height)
            var byteCount = bytesPerRow * heightPow2

            // If the texture will have mipmaps, add 1/3 to account for the bytes used by texture image level 1 through
            // texture image level N.
            if (hasMipMap) byteCount += byteCount / 3
            return byteCount
        }
    }

    val coordTransform = Matrix3()
    val byteCount get() = estimateByteCount(width, height, format, type, hasMipMap)
    protected var name = KglTexture.NONE
    protected var parameters: MutableMap? = null
    protected open val hasMipMap = false
    private var pickMode = false

    fun getTexParameter(name: Int) = parameters?.get(name)?:0

    fun setTexParameter(name: Int, param: Int) {
        val parameters = parameters ?: mutableMapOf().also { parameters = it }
        parameters[name] = param
    }

    override fun release(dc: DrawContext) {
        if (name.isValid()) deleteTexture(dc)
    }

    fun getTextureName(dc: DrawContext): KglTexture {
        if (!name.isValid()) createTexture(dc)
        return name
    }

    fun bindTexture(dc: DrawContext): Boolean {
        if (!name.isValid()) createTexture(dc)
        if (name.isValid()) dc.bindTexture(name)
        if (name.isValid() && pickMode != dc.isPickMode) {
            setTexParameters(dc)
            pickMode = dc.isPickMode
        }
        return name.isValid()
    }

    protected open fun createTexture(dc: DrawContext) {
        val currentTexture = dc.currentTexture
        try {
            // Create the OpenGL texture 2D object.
            name = dc.gl.createTexture()
            dc.gl.bindTexture(GL_TEXTURE_2D, name)

            // Specify the texture object's image data
            allocTexImage(dc)

            // Configure the texture object's parameters.
            setTexParameters(dc)
        } finally {
            // Restore the current OpenGL texture object binding.
            dc.gl.bindTexture(GL_TEXTURE_2D, currentTexture)
        }
    }

    protected open fun deleteTexture(dc: DrawContext) {
        dc.gl.deleteTexture(name)
        name = KglTexture.NONE
    }

    protected open fun allocTexImage(dc: DrawContext) {
        // Following line of code is a dirty hack to disable AFBC compression on Mali GPU driver,
        // which cause huge memory leak during surface shapes drawing on terrain textures.
        if (isRT and dc.gl.hasMaliOOMBug) dc.gl.texImage2D(GL_TEXTURE_2D, 0, format, 1, 1, 0, format, type, null)

        // Allocate texture memory for the OpenGL texture 2D object. The texture memory is initialized with 0.
        dc.gl.texImage2D(
            GL_TEXTURE_2D, 0 /*level*/, format, width, height, 0 /*border*/, format, type, null /*pixels*/
        )
    }

    // TODO refactor setTexParameters to apply all configured tex parameters
    // TODO apply defaults only when no parameter is configured
    // TODO consider simplifying the defaults and requiring that layers/shapes specify what they want
    protected open fun setTexParameters(dc: DrawContext) {
        var param: Int

        // Configure the OpenGL texture minification function. Always use the nearest filtering function in picking mode.
        when {
            dc.isPickMode -> dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
            getTexParameter(GL_TEXTURE_MIN_FILTER).also { param = it } != 0 ->
                dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, param)
            else -> dc.gl.texParameteri(
                GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, if (hasMipMap) GL_LINEAR_MIPMAP_LINEAR else GL_LINEAR
            )
        }

        // Configure the OpenGL texture magnification function. Always use the nearest filtering function in picking mode.
        when {
            dc.isPickMode -> dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
            getTexParameter(GL_TEXTURE_MAG_FILTER).also { param = it } != 0 ->
                dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, param)
            else -> dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        }

        // Configure the OpenGL texture wrapping function for texture coordinate S. Default to the edge clamping
        // function to render image tiles without seams.
        if (getTexParameter(GL_TEXTURE_WRAP_S).also { param = it } != 0)
            dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, param)
        else dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)

        // Configure the OpenGL texture wrapping function for texture coordinate T. Default to the edge clamping
        // function to render image tiles without seams.
        if (getTexParameter(GL_TEXTURE_WRAP_T).also { param = it } != 0)
            dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, param)
        else dc.gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy