commonMain.korlibs.graphics.AGObjects.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 korlibs.graphics
import korlibs.graphics.gl.*
import korlibs.graphics.shader.*
import korlibs.image.bitmap.*
import korlibs.io.lang.*
import korlibs.logger.*
import korlibs.math.geom.*
import korlibs.memory.*
import korlibs.memory.unit.*
internal interface AGNativeObject {
fun markToDelete()
}
open class AGObject : Closeable {
internal var _native: AGNativeObject? = null
internal var _cachedContextVersion: Int = -1
internal var _cachedVersion: Int = -2
internal var _version: Int = -1
internal fun _resetVersion() {
//_version = RESET_VERSION
markAsDirty()
_cachedVersion = _version - 1
}
protected fun markAsDirty() {
_version++
}
override fun close() {
_native?.markToDelete()
_native = null
}
}
class AGBuffer : AGObject() {
var mem: Buffer? = null
private set
// @TODO: Allow upload range in addition to the full buffer.
// @TODO: This will allow to upload chunks of uniform buffers for example.
// glBufferData & glBufferSubData
fun upload(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): AGBuffer = upload(Int8Buffer(data, offset, length).buffer)
fun upload(data: FloatArray, offset: Int = 0, length: Int = data.size - offset): AGBuffer = upload(Float32Buffer(data, offset, length).buffer)
fun upload(data: IntArray, offset: Int = 0, length: Int = data.size - offset): AGBuffer = upload(Int32Buffer(data, offset, length).buffer)
fun upload(data: ShortArray, offset: Int = 0, length: Int = data.size - offset): AGBuffer = upload(Int16Buffer(data, offset, length).buffer)
fun upload(data: Buffer, offset: Int, length: Int = data.size - offset): AGBuffer = upload(data.sliceWithSize(offset, length))
fun upload(data: Buffer): AGBuffer {
//println(data.sizeInBytes)
// Only check small buffers
if (data.sizeInBytes < 1024) {
if (this.mem != null && this.mem!!.sizeInBytes == data.sizeInBytes && arrayequal(this.mem!!, 0, data, 0, data.sizeInBytes)) return this
}
//println("New Data!")
mem = data.clone()
markAsDirty()
return this
}
//private val id = LAST_ID.incrementAndGet()
//companion object { private val LAST_ID = KorAtomicInt(0) }
// init { printStackTrace() }
override fun toString(): String = "AGBuffer(${mem?.sizeInBytes ?: 0})"
//override fun toString(): String = "AGBuffer[$id](${mem?.sizeInBytes ?: 0})"
}
data class AGTextureUnits(val textures: Array, val infos: AGTextureUnitInfoArray) {
companion object {
val MAX_TEXTURE_UNITS = 14
val EMPTY get() = AGTextureUnits()
}
constructor(size: Int = MAX_TEXTURE_UNITS) : this(arrayOfNulls(size), AGTextureUnitInfoArray(size))
val size: Int get() = textures.size
fun copyFrom(other: AGTextureUnits, sampler: Sampler) = copyFrom(other, sampler.index)
fun copyFrom(other: AGTextureUnits, index: Int) = set(index, other.textures[index], other.infos[index])
fun set(index: Int, texture: AGTexture?, info: AGTextureUnitInfo = AGTextureUnitInfo.DEFAULT) {
textures[index] = texture
infos[index] = info
}
fun set(sampler: Sampler, texture: AGTexture?, info: AGTextureUnitInfo = AGTextureUnitInfo.DEFAULT) {
set(sampler.index, texture, info)
}
fun clear() {
for (n in 0 until size) set(n, null, AGTextureUnitInfo.DEFAULT)
}
fun clone(): AGTextureUnits = AGTextureUnits(textures.copyOf(), infos.copyOf())
inline fun fastForEach(block: (index: Int, tex: AGTexture?, info: AGTextureUnitInfo) -> Unit) {
for (n in 0 until size) {
block(n, textures[n], infos[n])
}
}
}
inline class AGTextureUnitInfoArray(val data: IntArray) {
constructor(size: Int) : this(IntArray(size) { AGTextureUnitInfo.DEFAULT.data })
operator fun set(index: Int, value: AGTextureUnitInfo) { data[index] = value.data }
operator fun get(index: Int): AGTextureUnitInfo = AGTextureUnitInfo.fromRaw(data[index])
fun copyOf() = AGTextureUnitInfoArray(data.copyOf())
}
inline class AGTextureUnitInfo private constructor(val data: Int) {
companion object {
val INVALID = AGTextureUnitInfo(-1)
val DEFAULT = AGTextureUnitInfo(0)
.withLinearTrilinear(true, true)
.withWrap(AGWrapMode.CLAMP_TO_EDGE)
.withKind(AGTextureTargetKind.TEXTURE_2D)
fun fromRaw(data: Int): AGTextureUnitInfo = AGTextureUnitInfo(data)
operator fun invoke(
wrap: AGWrapMode = AGWrapMode.CLAMP_TO_EDGE,
linear: Boolean = true,
trilinear: Boolean = linear
): AGTextureUnitInfo = AGTextureUnitInfo(0).withWrap(wrap).withLinearTrilinear(linear, trilinear)
}
val wrap: AGWrapMode get() = AGWrapMode(data.extract2(0))
val linear: Boolean get() = data.extract(2)
val trilinear: Boolean get() = data.extract(3)
val kind: AGTextureTargetKind get() = AGTextureTargetKind(data.extract(8, 5))
fun withKind(kind: AGTextureTargetKind): AGTextureUnitInfo = AGTextureUnitInfo(data.insert5(kind.ordinal, 8))
fun withWrap(wrap: AGWrapMode): AGTextureUnitInfo = AGTextureUnitInfo(data.insert2(wrap.ordinal, 0))
fun withLinear(linear: Boolean): AGTextureUnitInfo = AGTextureUnitInfo(data.insert(linear, 2))
fun withTrilinear(trilinear: Boolean): AGTextureUnitInfo = AGTextureUnitInfo(data.insert(trilinear, 3))
fun withLinearTrilinear(linear: Boolean, trilinear: Boolean): AGTextureUnitInfo = withLinear(linear).withTrilinear(trilinear)
override fun toString(): String = "AGTextureUnitInfo(wrap=$wrap, linear=$linear, trilinear=$trilinear, kind=$kind)"
}
class AGTexture(
val targetKind: AGTextureTargetKind = AGTextureTargetKind.TEXTURE_2D
) : AGObject(), Closeable {
private val logger = Logger("AGTexture")
var isFbo: Boolean = false
var requestMipmaps: Boolean = false
var baseMipmapLevel: Int? = null
var maxMipmapLevel: Int? = null
/** [MultiBitmap] for multiple bitmaps (ie. cube map) */
var bitmap: Bitmap? = null
var mipmaps: Boolean = false; internal set
var forcedTexId: ForcedTexId? = null
val implForcedTexId: Int get() = forcedTexId?.forcedTexId ?: -1
val implForcedTexTarget: AGTextureTargetKind get() = forcedTexId?.forcedTexTarget?.let { AGTextureTargetKind.fromGl(it) } ?: targetKind
var estimatedMemoryUsage: ByteUnits = ByteUnits.fromBytes(0L)
val width: Int get() = bitmap?.width ?: 0
val height: Int get() = bitmap?.height ?: 0
val depth: Int get() = (bitmap as? MultiBitmap?)?.bitmaps?.size ?: 1
private fun checkBitmaps(bmp: Bitmap) {
if (!bmp.premultiplied) {
logger.error { "Trying to upload a non-premultiplied bitmap: $bmp. This will cause rendering artifacts" }
}
}
fun upload(bmp: Bitmap?, mipmaps: Boolean = false, baseMipmapLevel: Int? = null, maxMipmapLevel: Int? = null): AGTexture {
bmp?.let { checkBitmaps(it) }
this.forcedTexId = (bmp as? ForcedTexId?)
this.bitmap = bmp
estimatedMemoryUsage = ByteUnits.fromBytes(width * height * depth * 4)
markAsDirty()
this.requestMipmaps = mipmaps
this.baseMipmapLevel = baseMipmapLevel
this.maxMipmapLevel = maxMipmapLevel
return this
}
fun upload(bmp: BmpSlice?, mipmaps: Boolean = false, baseMipmapLevel: Int? = null, maxMipmapLevel: Int? = null): AGTexture {
// @TODO: Optimize to avoid copying?
return upload(bmp?.extract(), mipmaps, baseMipmapLevel, maxMipmapLevel)
}
fun doMipmaps(bitmap: Bitmap?, requestMipmaps: Boolean): Boolean {
val width = bitmap?.width ?: 0
val height = bitmap?.height ?: 0
return requestMipmaps && width.isPowerOfTwo && height.isPowerOfTwo
}
override fun toString(): String = "AGTexture(size=$width,$height)"
}
open class AGFrameBufferBase(val isMain: Boolean) : AGObject() {
val isTexture: Boolean get() = !isMain
val tex: AGTexture = AGTexture().also { it.isFbo = true }
var estimatedMemoryUsage: ByteUnits = ByteUnits.fromBytes(0)
override fun close() {
tex.close()
//ag.frameRenderBuffers -= this
}
override fun toString(): String = "AGFrameBufferBase(isMain=$isMain)"
}
open class AGFrameBuffer(val base: AGFrameBufferBase, val id: Int = -1) : Closeable {
constructor(isMain: Boolean = false, id: Int = -1) : this(AGFrameBufferBase(isMain), id)
val isTexture: Boolean get() = base.isTexture
val isMain: Boolean get() = base.isMain
val tex: AGTexture get() = base.tex
val info: AGFrameBufferInfo get() = AGFrameBufferInfo(0).withSize(width, height).withSamples(nsamples).withHasDepth(hasDepth).withHasStencil(hasStencil)
companion object {
const val DEFAULT_INITIAL_WIDTH = 128
const val DEFAULT_INITIAL_HEIGHT = 128
}
var nsamples: Int = 1; protected set
val hasStencilAndDepth: Boolean get() = hasDepth && hasStencil
var hasStencil: Boolean = true; protected set
var hasDepth: Boolean = true; protected set
var x = 0
var y = 0
var width = DEFAULT_INITIAL_WIDTH
var height = DEFAULT_INITIAL_HEIGHT
var fullWidth = DEFAULT_INITIAL_WIDTH
var fullHeight = DEFAULT_INITIAL_HEIGHT
private var _scissor = RectangleInt()
var scissor: RectangleInt? = null
open fun setSize(width: Int, height: Int) {
setSize(0, 0, width, height)
}
open fun setSize(x: Int, y: Int, width: Int, height: Int, fullWidth: Int = width, fullHeight: Int = height) {
if (this.x == x && this.y == y && this.width == width && this.height == height && this.fullWidth == fullWidth && this.fullHeight == fullHeight) return
tex.upload(NullBitmap(width, height))
base.estimatedMemoryUsage = ByteUnits.fromBytes(fullWidth * fullHeight * (4 + 4))
this.x = x
this.y = y
this.width = width
this.height = height
this.fullWidth = fullWidth
this.fullHeight = fullHeight
markAsDirty()
}
fun scissor(scissor: RectangleInt?) {
this.scissor = scissor
}
override fun close() {
base.close()
//ag.frameRenderBuffers -= this
}
fun setSamples(samples: Int) {
if (this.nsamples == samples) return
nsamples = samples
markAsDirty()
}
fun setExtra(hasDepth: Boolean = true, hasStencil: Boolean = true) {
if (this.hasDepth == hasDepth && this.hasStencil == hasStencil) return
this.hasDepth = hasDepth
this.hasStencil = hasStencil
markAsDirty()
}
private fun markAsDirty() {
//base.markAsDirty()
}
override fun toString(): String = "AGFrameBuffer(${if (isMain) "main" else "$id"}, $width, $height)"
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy