293 lines
8.4 KiB
Kotlin
293 lines
8.4 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.io.ImageData
|
|
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>") : AutoCloseable {
|
|
init {
|
|
state.ensureSameThread()
|
|
}
|
|
|
|
val pointer = glGenTextures()
|
|
|
|
init {
|
|
checkForGLError()
|
|
}
|
|
|
|
private val cleanable = 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 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: ImageData): 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
|
|
}
|
|
|
|
var isValid = true
|
|
private set
|
|
|
|
override fun close() {
|
|
state.ensureSameThread()
|
|
|
|
if (!isValid)
|
|
return
|
|
|
|
if (state.texture2D == this) {
|
|
state.texture2D = null
|
|
}
|
|
|
|
cleanable.clean()
|
|
isValid = false
|
|
}
|
|
|
|
companion object {
|
|
private val LOGGER = LogManager.getLogger()
|
|
}
|
|
}
|