KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt

276 lines
8.2 KiB
Kotlin

package ru.dbotthepony.kstarbound.client.gl
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL46.*
import org.lwjgl.stb.STBImage
import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kvector.vector.Vector2i
import java.io.File
import java.io.FileNotFoundException
import java.nio.ByteBuffer
import kotlin.reflect.KProperty
class TextureLoadingException(message: String) : Throwable(message)
data class UVCoord(val u: Float, val v: Float)
private class GLTexturePropertyTracker(private val flag: Int, private var value: Int) {
operator fun getValue(thisRef: GLTexture2D, property: KProperty<*>): Int {
return value
}
operator fun setValue(thisRef: GLTexture2D, property: KProperty<*>, value: Int) {
thisRef.state.ensureSameThread()
if (this.value == value) return
this.value = value
glTextureParameteri(thisRef.pointer, flag, value)
checkForGLError()
}
}
@Suppress("SameParameterValue")
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
init {
state.ensureSameThread()
}
val pointer = glGenTextures()
init {
checkForGLError()
state.registerCleanable(this, ::glDeleteTextures, pointer)
}
var width = 0
private set
var height = 0
private set
var uploaded = false
private set
val aspectRatioWH: Float get() {
if (height == 0) {
return 1f
}
return width.toFloat() / height.toFloat()
}
val aspectRatioHW: Float get() {
if (width == 0) {
return 1f
}
return height.toFloat() / width.toFloat()
}
var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR)
var textureMagFilter by GLTexturePropertyTracker(GL_TEXTURE_MAG_FILTER, GL_LINEAR)
var maxLevel by GLTexturePropertyTracker(GL_TEXTURE_MAX_LEVEL, 1000)
var textureWrapS by GLTexturePropertyTracker(GL_TEXTURE_WRAP_S, GL_REPEAT)
var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT)
fun bind(): GLTexture2D {
state.texture2D = this
return this
}
fun generateMips(): GLTexture2D {
state.ensureSameThread()
glGenerateTextureMipmap(pointer)
checkForGLError("Generating texture mipmaps")
return this
}
fun pixelToUV(x: Float, y: Float): UVCoord {
check(uploaded) { "Texture is not uploaded to be used" }
return UVCoord(x / width, y / height)
}
fun pixelToUV(x: Int, y: Int): UVCoord {
check(uploaded) { "Texture is not uploaded to be used" }
return UVCoord(x.toFloat() / width, y.toFloat() / height)
}
fun pixelToUV(pos: Vector2i) = pixelToUV(pos.x, pos.y)
fun allocate(mipmap: Int, loadedFormat: Int, width: Int, height: Int): GLTexture2D {
bind()
require(width > 0) { "Invalid width $width" }
require(height > 0) { "Invalid height $height" }
this.width = width
this.height = height
glTexImage2D(GL_TEXTURE_2D, mipmap, loadedFormat, width, height, 0, loadedFormat, GL_UNSIGNED_BYTE, 0L)
checkForGLError()
uploaded = true
return this
}
fun allocate(loadedFormat: Int, width: Int, height: Int): GLTexture2D {
return allocate(0, loadedFormat, width, height)
}
private fun upload(mipmap: Int, loadedFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D {
bind()
require(width > 0) { "Invalid width $width" }
require(height > 0) { "Invalid height $height" }
this.width = width
this.height = height
glTexImage2D(GL_TEXTURE_2D, mipmap, loadedFormat, width, height, 0, bufferFormat, dataFormat, data)
checkForGLError()
uploaded = true
return this
}
private fun upload(mipmap: Int, memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
bind()
require(width > 0) { "Invalid width $width" }
require(height > 0) { "Invalid height $height" }
this.width = width
this.height = height
glTexImage2D(GL_TEXTURE_2D, mipmap, memoryFormat, width, height, 0, bufferFormat, dataFormat, data)
checkForGLError()
uploaded = true
return this
}
fun upload(memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D {
return upload(0, memoryFormat, width, height, bufferFormat, dataFormat, data)
}
fun upload(memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
return upload(0, memoryFormat, width, height, bufferFormat, dataFormat, data)
}
fun upload(path: File, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
state.ensureSameThread()
if (!path.exists()) {
throw FileNotFoundException("${path.absolutePath} does not exist")
}
if (!path.isFile) {
throw FileNotFoundException("${path.absolutePath} is not a file")
}
val getwidth = intArrayOf(0)
val getheight = intArrayOf(0)
val getchannels = intArrayOf(0)
val bytes = STBImage.stbi_load(path.absolutePath, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${path.absolutePath}. Is it a valid image?")
require(getwidth[0] > 0) { "Image ${path.absolutePath} has bad width of ${getwidth[0]}" }
require(getheight[0] > 0) { "Image ${path.absolutePath} has bad height of ${getheight[0]}" }
upload(memoryFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
STBImage.stbi_image_free(bytes)
return this
}
fun upload(path: File): GLTexture2D {
state.ensureSameThread()
if (!path.exists()) {
throw FileNotFoundException("${path.absolutePath} does not exist")
}
if (!path.isFile) {
throw FileNotFoundException("${path.absolutePath} is not a file")
}
val getwidth = intArrayOf(0)
val getheight = intArrayOf(0)
val getchannels = intArrayOf(0)
val bytes = STBImage.stbi_load(path.absolutePath, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${path.absolutePath}. Is it a valid image?")
require(getwidth[0] > 0) { "Image ${path.absolutePath} has bad width of ${getwidth[0]}" }
require(getheight[0] > 0) { "Image ${path.absolutePath} has bad height of ${getheight[0]}" }
val bufferFormat = when (val numChannels = getchannels[0]) {
1 -> GL_R
2 -> GL_RG
3 -> GL_RGB
4 -> GL_RGBA
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
}
upload(bufferFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
STBImage.stbi_image_free(bytes)
return this
}
fun upload(buff: ByteBuffer, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
state.ensureSameThread()
val getwidth = intArrayOf(0)
val getheight = intArrayOf(0)
val getchannels = intArrayOf(0)
val bytes = STBImage.stbi_load_from_memory(buff, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${buff}. Is it a valid image?")
require(getwidth[0] > 0) { "Image '$name' has bad width of ${getwidth[0]}" }
require(getheight[0] > 0) { "Image '$name' has bad height of ${getheight[0]}" }
upload(memoryFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
STBImage.stbi_image_free(bytes)
return this
}
fun upload(buff: ByteBuffer): GLTexture2D {
state.ensureSameThread()
val getwidth = intArrayOf(0)
val getheight = intArrayOf(0)
val getchannels = intArrayOf(0)
val bytes = STBImage.stbi_load_from_memory(buff, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${buff}. Is it a valid image?")
require(getwidth[0] > 0) { "Image '$name' has bad width of ${getwidth[0]}" }
require(getheight[0] > 0) { "Image '$name' has bad height of ${getheight[0]}" }
val bufferFormat = when (val numChannels = getchannels[0]) {
1 -> GL_R
2 -> GL_RG
3 -> GL_RGB
4 -> GL_RGBA
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
}
upload(bufferFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
STBImage.stbi_image_free(bytes)
return this
}
fun upload(data: Image): GLTexture2D {
state.ensureSameThread()
val bufferFormat = when (val numChannels = data.amountOfChannels) {
1 -> GL_R
2 -> GL_RG
3 -> GL_RGB
4 -> GL_RGBA
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
}
upload(bufferFormat, data.width, data.height, bufferFormat, GL_UNSIGNED_BYTE, data.data)
return this
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}