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

commonMain.com.soywiz.korag.AG.kt Maven / Gradle / Ivy

There is a newer version: 4.0.10
Show newest version
package com.soywiz.korag

import com.soywiz.kds.*
import com.soywiz.kgl.KmlGl
import com.soywiz.klogger.*
import com.soywiz.kmem.*
import com.soywiz.korag.shader.*
import com.soywiz.korim.bitmap.*
import com.soywiz.korim.color.*
import com.soywiz.korio.async.*
import com.soywiz.korio.lang.*
import com.soywiz.korma.geom.*
import kotlin.coroutines.*
import kotlinx.coroutines.*

interface AGFactory {
    val supportsNativeFrame: Boolean
    fun create(nativeControl: Any?, config: AGConfig): AG
    fun createFastWindow(title: String, width: Int, height: Int): AGWindow
    //fun createFastWindow(title: String, width: Int, height: Int, config: AGConfig): AGWindow
}

data class AGConfig(val antialiasHint: Boolean = true)

interface AGContainer {
    val ag: AG
    //data class Resized(var width: Int, var height: Int) {
    //	fun setSize(width: Int, height: Int): Resized = this.apply {
    //		this.width = width
    //		this.height = height
    //	}
    //}

    fun repaint(): Unit
}

interface AGWindow : AGContainer {
}

interface AGFeatures {
    val graphicExtensions: Set get() = emptySet()
    val isInstancedSupported: Boolean get() = false
    val isFloatTextureSupported: Boolean get() = false
}

abstract class AG : AGFeatures, Extra by Extra.Mixin() {
    var contextVersion = 0
    abstract val nativeComponent: Any

    open fun contextLost() {
        Console.info("AG.contextLost()", this)
        contextVersion++
    }

    open val maxTextureSize = Size(2048, 2048)

    open val devicePixelRatio: Double = 1.0
    open val pixelsPerInch: Double get() = 96.0

    open fun beforeDoRender() {
    }

    inline fun doRender(block: () -> Unit) {
        beforeDoRender()
        mainRenderBuffer.init()
        setRenderBufferTemporally(mainRenderBuffer) {
            block()
        }
    }

    open fun offscreenRendering(callback: () -> Unit) {
        callback()
    }

    open fun repaint() {
    }

    open fun resized(width: Int, height: Int) {
        mainRenderBuffer.setSize(0, 0, width, height, width, height)
    }

    open fun resized(x: Int, y: Int, width: Int, height: Int, fullWidth: Int, fullHeight: Int) {
        mainRenderBuffer.setSize(x, y, width, height, fullWidth, fullHeight)
    }

    open fun dispose() {
    }

    // On MacOS components, this will be the size of the component
    open val backWidth: Int get() = mainRenderBuffer.width
    open val backHeight: Int get() = mainRenderBuffer.height

    // On MacOS components, this will be the full size of the window
    val realBackWidth get() = mainRenderBuffer.fullWidth
    val realBackHeight get() = mainRenderBuffer.fullHeight

    val currentWidth: Int get() = currentRenderBuffer?.width ?: mainRenderBuffer.width
    val currentHeight: Int get() = currentRenderBuffer?.height ?: mainRenderBuffer.height

    //protected fun setViewport(v: IntArray) = setViewport(v[0], v[1], v[2], v[3])

    enum class BlendEquation {
        ADD, SUBTRACT, REVERSE_SUBTRACT
    }

    enum class BlendFactor {
        DESTINATION_ALPHA,
        DESTINATION_COLOR,
        ONE,
        ONE_MINUS_DESTINATION_ALPHA,
        ONE_MINUS_DESTINATION_COLOR,
        ONE_MINUS_SOURCE_ALPHA,
        ONE_MINUS_SOURCE_COLOR,
        SOURCE_ALPHA,
        SOURCE_COLOR,
        ZERO;
    }

    data class Scissor(
        var x: Int, var y: Int,
        var width: Int, var height: Int
    ) {
        val rect: Rectangle = Rectangle()
            get() {
                field.setTo(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble())
                return field
            }

        val top get() = y
        val left get() = x
        val right get() = x + width
        val bottom get() = y + height

        fun copyFrom(that: Scissor): Scissor = setTo(that.x, that.y, that.width, that.height)

        fun setTo(x: Int, y: Int, width: Int, height: Int): Scissor = this.apply {
            this.x = x
            this.y = y
            this.width = width
            this.height = height
        }
    }

    data class Blending(
        val srcRGB: BlendFactor,
        val dstRGB: BlendFactor,
        val srcA: BlendFactor = srcRGB,
        val dstA: BlendFactor = dstRGB,
        val eqRGB: BlendEquation = BlendEquation.ADD,
        val eqA: BlendEquation = eqRGB
    ) {
        constructor(src: BlendFactor, dst: BlendFactor, eq: BlendEquation = BlendEquation.ADD) : this(
            src, dst,
            src, dst,
            eq, eq
        )

        val disabled: Boolean get() = srcRGB == BlendFactor.ONE && dstRGB == BlendFactor.ZERO && srcA == BlendFactor.ONE && dstA == BlendFactor.ZERO
        val enabled: Boolean get() = !disabled

        companion object {
            val NONE = Blending(BlendFactor.ONE, BlendFactor.ZERO, BlendFactor.ONE, BlendFactor.ZERO)
            val NORMAL = Blending(
                BlendFactor.SOURCE_ALPHA, BlendFactor.ONE_MINUS_SOURCE_ALPHA,
                BlendFactor.ONE, BlendFactor.ONE_MINUS_SOURCE_ALPHA
            )
            val ADD = Blending(
                BlendFactor.SOURCE_ALPHA, BlendFactor.DESTINATION_ALPHA,
                BlendFactor.ONE, BlendFactor.ONE
            )
        }
    }

    interface BitmapSourceBase {
        val rgba: Boolean
        val width: Int
        val height: Int
    }

    class SyncBitmapSource(
        override val rgba: Boolean,
        override val width: Int,
        override val height: Int,
        val gen: () -> Bitmap?
    ) : BitmapSourceBase {
        companion object {
            val NIL = SyncBitmapSource(true, 0, 0) { null }
        }

        override fun toString(): String = "SyncBitmapSource(rgba=$rgba, width=$width, height=$height)"
    }

    class AsyncBitmapSource(
        val coroutineContext: CoroutineContext,
        override val rgba: Boolean,
        override val width: Int,
        override val height: Int,
        val gen: suspend () -> Bitmap?
    ) : BitmapSourceBase {
        companion object {
            val NIL = AsyncBitmapSource(EmptyCoroutineContext, true, 0, 0) { null }
        }
    }

    var lastTextureId = 0
    var createdTextureCount = 0
    var deletedTextureCount = 0

    enum class TextureKind { RGBA, LUMINANCE }

    enum class TextureTargetKind { TEXTURE_2D, TEXTURE_3D, TEXTURE_CUBE_MAP } //TODO: there are other possible values

    //TODO: would it better if this was an interface ?
    open inner class Texture : Closeable {
        var isFbo = false
        open val premultiplied = true
        var requestMipmaps = false
        var mipmaps = false; protected set
        var source: BitmapSourceBase = SyncBitmapSource.NIL
        private var uploaded: Boolean = false
        private var generating: Boolean = false
        private var generated: Boolean = false
        private var tempBitmap: Bitmap? = null
        var ready: Boolean = false; private set
        val texId = lastTextureId++

        init {
            createdTextureCount++
        }

        protected fun invalidate() {
            uploaded = false
            generating = false
            generated = false
        }

        fun upload(bmp: Bitmap?, mipmaps: Boolean = false): Texture {
            return upload(
                if (bmp != null) SyncBitmapSource(
                    rgba = bmp.bpp > 8,
                    width = bmp.width,
                    height = bmp.height
                ) { bmp } else SyncBitmapSource.NIL, mipmaps)
        }

        fun upload(bmp: BitmapSlice?, mipmaps: Boolean = false): Texture {
            // @TODO: Optimize to avoid copying?
            return upload(bmp?.extract(), mipmaps)
        }

        fun upload(source: BitmapSourceBase, mipmaps: Boolean = false): Texture = this.apply {
            this.source = source
            uploadedSource()
            invalidate()
            this.requestMipmaps = mipmaps
        }

        protected open fun uploadedSource() {
        }

        open fun bind() {
        }

        open fun unbind() {
        }

        fun manualUpload() = this.apply {
            uploaded = true
        }

        fun bindEnsuring() {
            bind()
            if (isFbo) return
            val source = this.source
            if (uploaded) return

            if (!generating) {
                generating = true
                when (source) {
                    is SyncBitmapSource -> {
                        tempBitmap = source.gen()
                        generated = true
                    }
                    is AsyncBitmapSource -> {
                        launchImmediately(source.coroutineContext) {
                            tempBitmap = source.gen()
                            generated = true
                        }
                    }
                }
            }

            if (generated) {
                uploaded = true
                generating = false
                generated = false
                actualSyncUpload(source, tempBitmap, requestMipmaps)
                tempBitmap = null
                ready = true
            }
        }

        open fun actualSyncUpload(source: BitmapSourceBase, bmp: Bitmap?, requestMipmaps: Boolean) {
        }


        init {
            //Console.log("CREATED TEXTURE: $texId")
            //printTexStats()
        }

        private var alreadyClosed = false
        override fun close() {
            if (!alreadyClosed) {
                alreadyClosed = true
                source = SyncBitmapSource.NIL
                tempBitmap = null
                deletedTextureCount++
                //Console.log("CLOSED TEXTURE: $texId")
                //printTexStats()
            }
        }

        private fun printTexStats() {
            //Console.log("create=$createdCount, delete=$deletedCount, alive=${createdCount - deletedCount}")
        }
    }

    data class TextureUnit(
        var texture: AG.Texture? = null,
        var linear: Boolean = true
    )

    open class Buffer(val kind: Kind) : Closeable {
        enum class Kind { INDEX, VERTEX }

        var dirty = false
        protected var mem: FBuffer? = null
        protected var memOffset: Int = 0
        protected var memLength: Int = 0

        open fun afterSetMem() {
        }

        fun upload(data: ByteArray, offset: Int = 0, length: Int = data.size): Buffer {
            mem = FBuffer(length)
            mem!!.setAlignedArrayInt8(0, data, offset, length)
            memOffset = 0
            memLength = length
            dirty = true
            afterSetMem()
            return this
        }

        fun upload(data: FloatArray, offset: Int = 0, length: Int = data.size): Buffer {
            mem = FBuffer(length * 4)
            mem!!.setAlignedArrayFloat32(0, data, offset, length)
            memOffset = 0
            memLength = length * 4
            dirty = true
            afterSetMem()
            return this
        }

        fun upload(data: IntArray, offset: Int = 0, length: Int = data.size): Buffer {
            mem = FBuffer(length * 4)
            mem!!.setAlignedArrayInt32(0, data, offset, length)
            memOffset = 0
            memLength = length * 4
            dirty = true
            afterSetMem()
            return this
        }

        fun upload(data: ShortArray, offset: Int = 0, length: Int = data.size): Buffer {
            mem = FBuffer(length * 2)
            mem!!.setAlignedArrayInt16(0, data, offset, length)
            memOffset = 0
            memLength = length * 2
            dirty = true
            afterSetMem()
            return this
        }

        fun upload(data: FBuffer, offset: Int = 0, length: Int = data.size): Buffer {
            mem = data
            memOffset = offset
            memLength = length
            dirty = true
            afterSetMem()
            return this
        }

        override fun close() {
            mem = null
            memOffset = 0
            memLength = 0
            dirty = true
        }
    }

    enum class DrawType {
        POINTS,
        LINE_STRIP,
        LINE_LOOP,
        LINES,
        TRIANGLES,
        TRIANGLE_STRIP,
        TRIANGLE_FAN,
    }

    enum class IndexType {
        UBYTE, USHORT,
        // https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements
        @Deprecated("UINT is not always supported on webgl")
        UINT
    }

    val dummyTexture by lazy { createTexture() }

    fun createTexture(): Texture = createTexture(premultiplied = true)
    fun createTexture(bmp: Bitmap, mipmaps: Boolean = false): Texture = createTexture(bmp.premultiplied).upload(bmp, mipmaps)
    fun createTexture(bmp: BitmapSlice, mipmaps: Boolean = false): Texture =
        createTexture(bmp.premultiplied).upload(bmp, mipmaps)

    fun createTexture(bmp: Bitmap, mipmaps: Boolean = false, premultiplied: Boolean = true): Texture =
        createTexture(premultiplied).upload(bmp, mipmaps)

    open fun createTexture(premultiplied: Boolean): Texture = Texture()

    open fun createTexture(targetKind: TextureTargetKind, init:Texture.(gl:KmlGl)->Unit): Texture = Texture()

    open fun createBuffer(kind: Buffer.Kind) = Buffer(kind)
    fun createIndexBuffer() = createBuffer(Buffer.Kind.INDEX)
    fun createVertexBuffer() = createBuffer(Buffer.Kind.VERTEX)

    fun createVertexData(vararg attributes: Attribute, layoutSize: Int? = null) = AG.VertexData(createVertexBuffer(), VertexLayout(*attributes, layoutSize = layoutSize))

    fun createIndexBuffer(data: ShortArray, offset: Int = 0, length: Int = data.size - offset) =
        createIndexBuffer().apply {
            upload(data, offset, length)
        }

    fun createIndexBuffer(data: FBuffer, offset: Int = 0, length: Int = data.size - offset) =
        createIndexBuffer().apply {
            upload(data, offset, length)
        }

    fun createVertexBuffer(data: FloatArray, offset: Int = 0, length: Int = data.size - offset) =
        createVertexBuffer().apply {
            upload(data, offset, length)
        }

    fun createVertexBuffer(data: FBuffer, offset: Int = 0, length: Int = data.size - offset) =
        createVertexBuffer().apply {
            upload(data, offset, length)
        }

    enum class StencilOp {
        DECREMENT_SATURATE,
        DECREMENT_WRAP,
        INCREMENT_SATURATE,
        INCREMENT_WRAP,
        INVERT,
        KEEP,
        SET,
        ZERO;
    }

    enum class TriangleFace {
        FRONT, BACK, FRONT_AND_BACK, NONE;
    }

    enum class CompareMode {
        ALWAYS, EQUAL, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL, NEVER, NOT_EQUAL;
    }

    data class ColorMaskState(
        var red: Boolean = true,
        var green: Boolean = true,
        var blue: Boolean = true,
        var alpha: Boolean = true
    ) {
        //val enabled = !red || !green || !blue || !alpha
    }

    enum class FrontFace {
        BOTH, CW, CCW
    }

    data class RenderState(
        var depthFunc: CompareMode = CompareMode.ALWAYS,
        var depthMask: Boolean = true,
        var depthNear: Float = 0f,
        var depthFar: Float = 1f,
        var lineWidth: Float = 1f,
        var frontFace: FrontFace = FrontFace.BOTH
    )

    data class StencilState(
        var enabled: Boolean = false,
        var triangleFace: TriangleFace = TriangleFace.FRONT_AND_BACK,
        var compareMode: CompareMode = CompareMode.ALWAYS,
        var actionOnBothPass: StencilOp = StencilOp.KEEP,
        var actionOnDepthFail: StencilOp = StencilOp.KEEP,
        var actionOnDepthPassStencilFail: StencilOp = StencilOp.KEEP,
        var referenceValue: Int = 0,
        var readMask: Int = 0xFF,
        var writeMask: Int = 0xFF
    ) {
        fun copyFrom(other: StencilState) {
            this.enabled = other.enabled
            this.triangleFace = other.triangleFace
            this.compareMode = other.compareMode
            this.actionOnBothPass = other.actionOnBothPass
            this.actionOnDepthFail = other.actionOnDepthFail
            this.actionOnDepthPassStencilFail = other.actionOnDepthPassStencilFail
            this.referenceValue = other.referenceValue
            this.readMask = other.readMask
            this.writeMask = other.writeMask
        }
    }

    private val dummyRenderState = RenderState()
    private val dummyStencilState = StencilState()
    private val dummyColorMaskState = ColorMaskState()

    //open val supportInstancedDrawing: Boolean get() = false

    fun draw(
        vertices: Buffer,
        program: Program,
        type: DrawType,
        vertexLayout: VertexLayout,
        vertexCount: Int,
        indices: Buffer? = null,
        indexType: IndexType = IndexType.USHORT,
        offset: Int = 0,
        blending: Blending = Blending.NORMAL,
        uniforms: UniformValues = UniformValues.EMPTY,
        stencil: StencilState = dummyStencilState,
        colorMask: ColorMaskState = dummyColorMaskState,
        renderState: RenderState = dummyRenderState,
        scissor: Scissor? = null,
        instances: Int = 1
    ) = draw(batch.also { batch ->
        batch.vertices = vertices
        batch.program = program
        batch.type = type
        batch.vertexLayout = vertexLayout
        batch.vertexCount = vertexCount
        batch.indices = indices
        batch.indexType = indexType
        batch.offset = offset
        batch.blending = blending
        batch.uniforms = uniforms
        batch.stencil = stencil
        batch.colorMask = colorMask
        batch.renderState = renderState
        batch.scissor = scissor
        batch.instances = instances
    })

    fun drawV2(
        vertexData: List,
        program: Program,
        type: DrawType,
        vertexCount: Int,
        indices: Buffer? = null,
        indexType: IndexType = IndexType.USHORT,
        offset: Int = 0,
        blending: Blending = Blending.NORMAL,
        uniforms: UniformValues = UniformValues.EMPTY,
        stencil: StencilState = dummyStencilState,
        colorMask: ColorMaskState = dummyColorMaskState,
        renderState: RenderState = dummyRenderState,
        scissor: Scissor? = null,
        instances: Int = 1
    ) = draw(batch.also { batch ->
        batch.vertexData = vertexData
        batch.program = program
        batch.type = type
        batch.vertexCount = vertexCount
        batch.indices = indices
        batch.indexType = indexType
        batch.offset = offset
        batch.blending = blending
        batch.uniforms = uniforms
        batch.stencil = stencil
        batch.colorMask = colorMask
        batch.renderState = renderState
        batch.scissor = scissor
        batch.instances = instances
    })

    data class VertexData(
        var buffer: Buffer = Buffer(Buffer.Kind.VERTEX),
        var layout: VertexLayout = VertexLayout()
    )

    data class Batch constructor(
        var vertexData: List = listOf(VertexData()),
        var program: Program = DefaultShaders.PROGRAM_DEBUG,
        var type: DrawType = DrawType.TRIANGLES,
        var vertexCount: Int = 0,
        var indices: Buffer? = null,
        var indexType: IndexType = IndexType.USHORT,
        var offset: Int = 0,
        var blending: Blending = Blending.NORMAL,
        var uniforms: UniformValues = UniformValues.EMPTY,
        var stencil: StencilState = StencilState(),
        var colorMask: ColorMaskState = ColorMaskState(),
        var renderState: RenderState = RenderState(),
        var scissor: Scissor? = null,
        var instances: Int = 1
    ) {
        private val singleVertexData = arrayListOf()

        private fun ensureSingleVertexData() {
            if (singleVertexData.isEmpty()) singleVertexData.add(VertexData())
            vertexData = singleVertexData
        }

        @Deprecated("Use vertexData instead")
        var vertices: Buffer
            get() = (singleVertexData.firstOrNull() ?: vertexData.first()).buffer
            set(value) {
                ensureSingleVertexData()
                singleVertexData[0].buffer = value
            }
        @Deprecated("Use vertexData instead")
        var vertexLayout: VertexLayout
            get() = (singleVertexData.firstOrNull() ?: vertexData.first()).layout
            set(value) {
                ensureSingleVertexData()
                singleVertexData[0].layout = value
            }
    }

    private val batch = Batch()

    open fun draw(batch: Batch) {
    }

    open fun disposeTemporalPerFrameStuff() = Unit

    val frameRenderBuffers = LinkedHashSet()
    val renderBuffers = Pool() { createRenderBuffer() }

    interface BaseRenderBuffer {
        val x: Int
        val y: Int
        val width: Int
        val height: Int
        val fullWidth: Int
        val fullHeight: Int
        val scissor: RectangleInt?
        fun setSize(x: Int, y: Int, width: Int, height: Int, fullWidth: Int = width, fullHeight: Int = height)
        fun init() = Unit
        fun set() = Unit
        fun unset() = Unit
        fun scissor(scissor: RectangleInt?)
    }

    open class BaseRenderBufferImpl : BaseRenderBuffer {
        override var x = 0
        override var y = 0
        override var width = 128
        override var height = 128
        override var fullWidth = 128
        override var fullHeight = 128
        private val _scissor = RectangleInt()
        override var scissor: RectangleInt? = null

        override fun setSize(x: Int, y: Int, width: Int, height: Int, fullWidth: Int, fullHeight: Int) {
            this.x = x
            this.y = y
            this.width = width
            this.height = height
            this.fullWidth = fullWidth
            this.fullHeight = fullHeight
        }

        override fun scissor(scissor: RectangleInt?) {
            this.scissor = scissor?.let { _scissor.setTo(it) }
        }
    }

    val mainRenderBuffer: BaseRenderBuffer by lazy { createMainRenderBuffer() }

    open fun createMainRenderBuffer() = BaseRenderBufferImpl()

    open inner class RenderBuffer : Closeable, BaseRenderBufferImpl() {
        open val id: Int = -1
        private var cachedTexVersion = -1
        private var _tex: Texture? = null

        val tex: AG.Texture
            get() {
                if (cachedTexVersion != contextVersion) {
                    cachedTexVersion = contextVersion
                    _tex = [email protected](premultiplied = true).manualUpload().apply { isFbo = true }
                }
                return _tex!!
            }

        protected var dirty = false

        override fun setSize(x: Int, y: Int, width: Int, height: Int, fullWidth: Int, fullHeight: Int) {
            super.setSize(x, y, width, height, fullWidth, fullHeight)
            dirty = true
        }

        override fun set(): Unit = Unit
        fun readBitmap(bmp: Bitmap32) = [email protected](bmp)
        fun readDepth(width: Int, height: Int, out: FloatArray): Unit = [email protected](width, height, out)
        override fun close() = Unit
    }

    open fun createRenderBuffer() = RenderBuffer()

    fun flip() {
        disposeTemporalPerFrameStuff()
        renderBuffers.free(frameRenderBuffers)
        if (frameRenderBuffers.isNotEmpty()) frameRenderBuffers.clear()
        flipInternal()
    }

    protected open fun flipInternal() = Unit

    open fun startFrame() {
    }

    open fun clear(
        color: RGBA = Colors.TRANSPARENT_BLACK,
        depth: Float = 1f,
        stencil: Int = 0,
        clearColor: Boolean = true,
        clearDepth: Boolean = true,
        clearStencil: Boolean = true
    ) = Unit

    @PublishedApi
    internal var currentRenderBuffer: BaseRenderBuffer? = null

    val renderingToTexture get() = currentRenderBuffer !== mainRenderBuffer && currentRenderBuffer !== null

    inline fun backupTexture(tex: Texture?, callback: () -> Unit) {
        if (tex != null) {
            readColorTexture(tex, backWidth, backHeight)
        }
        try {
            callback()
        } finally {
            if (tex != null) drawTexture(tex)
        }
    }

    inline fun setRenderBufferTemporally(rb: BaseRenderBuffer, callback: () -> Unit) {
        val old = setRenderBuffer(rb)
        try {
            callback()
        } finally {
            setRenderBuffer(old)
        }
    }

    inline fun renderToTexture(width: Int, height: Int, render: () -> Unit, use: (tex: Texture) -> Unit) {
        val rb = renderBuffers.alloc()
        frameRenderBuffers += rb

        try {
            rb.setSize(0, 0, width, height, width, height)
            setRenderBufferTemporally(rb) {
                clear(Colors.TRANSPARENT_BLACK) // transparent
                render()
            }

            use(rb.tex)
        } finally {
            frameRenderBuffers -= rb
            renderBuffers.free(rb)
        }
    }

    inline fun renderToBitmap(bmp: Bitmap32, render: () -> Unit) {
        renderToTexture(bmp.width, bmp.height, {
            render()
            readColor(bmp)
        }, {})
    }

    fun setRenderBuffer(renderBuffer: BaseRenderBuffer?): BaseRenderBuffer? {
        val old = currentRenderBuffer
        currentRenderBuffer?.unset()
        currentRenderBuffer = renderBuffer
        renderBuffer?.set()
        return old
    }

    open fun readColor(bitmap: Bitmap32): Unit = TODO()
    open fun readDepth(width: Int, height: Int, out: FloatArray): Unit = TODO()
    open fun readDepth(out: FloatArray2): Unit = readDepth(out.width, out.height, out.data)
    open fun readColorTexture(texture: Texture, width: Int = backWidth, height: Int = backHeight): Unit = TODO()
    fun readColor() = Bitmap32(backWidth, backHeight).apply { readColor(this) }
    fun readDepth() = FloatArray2(backWidth, backHeight) { 0f }.apply { readDepth(this) }

    inner class TextureDrawer {
        val VERTEX_COUNT = 4
        val vertices = createBuffer(AG.Buffer.Kind.VERTEX)
        val vertexLayout = VertexLayout(DefaultShaders.a_Pos, DefaultShaders.a_Tex)
        val verticesData = FBuffer(VERTEX_COUNT * vertexLayout.totalSize)
        val program = Program(VertexShader {
            DefaultShaders.apply {
                v_Tex setTo a_Tex
                out setTo vec4(a_Pos, 0f.lit, 1f.lit)
            }
        }, FragmentShader {
            DefaultShaders.apply {
                //out setTo vec4(1f, 1f, 0f, 1f)
                out setTo texture2D(u_Tex, v_Tex["xy"])
            }
        })
        val uniforms = UniformValues()

        fun setVertex(n: Int, px: Float, py: Float, tx: Float, ty: Float) {
            val offset = n * 4
            verticesData.setAlignedFloat32(offset + 0, px)
            verticesData.setAlignedFloat32(offset + 1, py)
            verticesData.setAlignedFloat32(offset + 2, tx)
            verticesData.setAlignedFloat32(offset + 3, ty)
        }

        fun draw(tex: Texture, left: Float, top: Float, right: Float, bottom: Float) {
            //tex.upload(Bitmap32(32, 32) { x, y -> Colors.RED })
            uniforms[DefaultShaders.u_Tex] = TextureUnit(tex)

            val texLeft = -1f
            val texRight = +1f
            val texTop = -1f
            val texBottom = +1f

            setVertex(0, left, top, texLeft, texTop)
            setVertex(1, right, top, texRight, texTop)
            setVertex(2, left, bottom, texLeft, texBottom)
            setVertex(3, right, bottom, texRight, texBottom)

            vertices.upload(verticesData)
            draw(
                vertices = vertices,
                program = program,
                type = AG.DrawType.TRIANGLE_STRIP,
                vertexLayout = vertexLayout,
                vertexCount = 4,
                uniforms = uniforms,
                blending = AG.Blending.NONE
            )
        }
    }

    val textureDrawer by lazy { TextureDrawer() }
    val flipRenderTexture = true

    fun drawTexture(tex: Texture) {
        textureDrawer.draw(tex, -1f, +1f, +1f, -1f)
    }

    private val drawTempTexture: Texture by lazy { createTexture() }

    fun drawBitmap(bmp: Bitmap) {
        drawTempTexture.upload(bmp, mipmaps = false)
        drawTexture(drawTempTexture)
        drawTempTexture.upload(Bitmaps.transparent)
    }

    class UniformValues() {
        companion object {
            internal val EMPTY = UniformValues()
        }

        private val _uniforms = FastArrayList()
        private val _values = FastArrayList()
        val uniforms = _uniforms as List

        val keys get() = uniforms
        val values = _values as List

        val size get() = _uniforms.size

        constructor(vararg pairs: Pair) : this() {
            for (pair in pairs) put(pair.first, pair.second)
        }

        operator fun plus(other: UniformValues): UniformValues {
            return UniformValues().put(this).put(other)
        }

        fun clear() {
            _uniforms.clear()
            _values.clear()
        }

        operator fun get(uniform: Uniform): Any? {
            for (n in 0 until _uniforms.size) {
                if (_uniforms[n].name == uniform.name) return _values[n]
            }
            return null
        }

        operator fun set(uniform: Uniform, value: Any) = put(uniform, value)

        fun putOrRemove(uniform: Uniform, value: Any?) {
            if (value == null) {
                remove(uniform)
            } else {
                put(uniform, value)
            }
        }

        fun put(uniform: Uniform, value: Any): UniformValues {
            for (n in 0 until _uniforms.size) {
                if (_uniforms[n].name == uniform.name) {
                    _values[n] = value
                    return this
                }
            }

            _uniforms.add(uniform)
            _values.add(value)
            return this
        }

        fun remove(uniform: Uniform) {
            for (n in 0 until _uniforms.size) {
                if (_uniforms[n].name == uniform.name) {
                    _uniforms.removeAt(n)
                    _values.removeAt(n)
                    return
                }
            }
        }

        fun put(uniforms: UniformValues): UniformValues {
            for (n in 0 until uniforms.size) {
                this.put(uniforms._uniforms[n], uniforms._values[n])
            }
            return this
        }

        fun setTo(uniforms: UniformValues) {
            clear()
            put(uniforms)
        }

        override fun toString() = "{" + keys.zip(values).map { "${it.first}=${it.second}" }.joinToString(", ") + "}"
    }
}


fun AG.Blending.toRenderFboIntoBack() = this
fun AG.Blending.toRenderImageIntoFbo() = this




© 2015 - 2025 Weber Informatics LLC | Privacy Policy