Geometry batching
This commit is contained in:
parent
1479f7483a
commit
178953bce7
@ -83,7 +83,7 @@ dependencies {
|
||||
implementation("com.github.jnr:jnr-ffi:2.2.13")
|
||||
|
||||
implementation("ru.dbotthepony:kbox2d:2.4.1.6")
|
||||
implementation("ru.dbotthepony:kvector:2.9.0")
|
||||
implementation("ru.dbotthepony:kvector:2.9.2")
|
||||
|
||||
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import org.lwjgl.glfw.Callbacks
|
||||
import org.lwjgl.glfw.GLFW
|
||||
import org.lwjgl.glfw.GLFWErrorCallback
|
||||
import org.lwjgl.opengl.GL
|
||||
import org.lwjgl.opengl.GL11
|
||||
import org.lwjgl.opengl.GL45.*
|
||||
import org.lwjgl.opengl.GLCapabilities
|
||||
import org.lwjgl.system.MemoryStack
|
||||
@ -30,6 +29,8 @@ import ru.dbotthepony.kstarbound.client.gl.properties.GLGenericTracker
|
||||
import ru.dbotthepony.kstarbound.client.gl.properties.GLObjectTracker
|
||||
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateIntTracker
|
||||
import ru.dbotthepony.kstarbound.client.gl.properties.GLStateSwitchTracker
|
||||
import ru.dbotthepony.kstarbound.client.gl.properties.GLTexturesTracker
|
||||
import ru.dbotthepony.kstarbound.client.gl.shader.FontProgram
|
||||
import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms
|
||||
import ru.dbotthepony.kstarbound.client.gl.shader.GLShader
|
||||
import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
|
||||
@ -72,6 +73,7 @@ import java.util.*
|
||||
import java.util.concurrent.locks.LockSupport
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class StarboundClient : Closeable {
|
||||
@ -260,15 +262,7 @@ class StarboundClient : Closeable {
|
||||
.weakValues()
|
||||
.build()
|
||||
|
||||
private val missingTexture: GLTexture2D by lazy {
|
||||
newTexture(missingTexturePath).upload(Starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also {
|
||||
it.textureMinFilter = GL_NEAREST
|
||||
it.textureMagFilter = GL_NEAREST
|
||||
}
|
||||
}
|
||||
|
||||
private val missingTexturePath = "/assetmissing.png"
|
||||
private val regularShaderPrograms = ArrayList<WeakReference<GLShaderProgram.Regular>>()
|
||||
private val fontShaderPrograms = ArrayList<WeakReference<FontProgram>>()
|
||||
private val uberShaderPrograms = ArrayList<WeakReference<UberShader>>()
|
||||
|
||||
val lightMapLocation = maxTextureBlocks - 1
|
||||
@ -278,8 +272,8 @@ class StarboundClient : Closeable {
|
||||
uberShaderPrograms.add(WeakReference(program))
|
||||
}
|
||||
|
||||
if (program is GLShaderProgram.Regular) {
|
||||
regularShaderPrograms.add(WeakReference(program))
|
||||
if (program is FontProgram) {
|
||||
fontShaderPrograms.add(WeakReference(program))
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,23 +333,7 @@ class StarboundClient : Closeable {
|
||||
var framebuffer by GLObjectTracker<GLFrameBuffer>(::glBindFramebuffer, GL_FRAMEBUFFER)
|
||||
var program by GLObjectTracker<GLShaderProgram>(::glUseProgram)
|
||||
|
||||
private val textures = Array(maxTextureBlocks) { GLObjectTracker<GLTexture2D>(GL11::glBindTexture, GL_TEXTURE_2D) }
|
||||
|
||||
var activeTexture = 0
|
||||
set(value) {
|
||||
ensureSameThread()
|
||||
|
||||
if (field != value) {
|
||||
require(value in 0 until maxTextureBlocks) { "Texture block out of bounds: $value (max texture blocks is $maxTextureBlocks)" }
|
||||
glActiveTexture(GL_TEXTURE0 + value)
|
||||
checkForGLError()
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
var texture2D: GLTexture2D?
|
||||
get() = textures[activeTexture].get()
|
||||
set(value) { textures[activeTexture].accept(value) }
|
||||
val textures2D = GLTexturesTracker<GLTexture2D>(maxTextureBlocks)
|
||||
|
||||
var clearColor by GLGenericTracker<IStruct4f>(RGBAColor.WHITE) {
|
||||
val (r, g, b, a) = it
|
||||
@ -376,7 +354,8 @@ class StarboundClient : Closeable {
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
val whiteTexture = GLTexture2D("white")
|
||||
val whiteTexture = GLTexture2D(1, 1, GL_RGB8)
|
||||
val missingTexture = GLTexture2D(8, 8, GL_RGB8)
|
||||
|
||||
init {
|
||||
val buffer = ByteBuffer.allocateDirect(3)
|
||||
@ -384,7 +363,42 @@ class StarboundClient : Closeable {
|
||||
buffer.put(0xFF.toByte())
|
||||
buffer.put(0xFF.toByte())
|
||||
buffer.position(0)
|
||||
whiteTexture.upload(GL_RGB, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, buffer)
|
||||
whiteTexture.upload(GL_RGB, GL_UNSIGNED_BYTE, buffer)
|
||||
}
|
||||
|
||||
init {
|
||||
val buffer = ByteBuffer.allocateDirect(3 * 8 * 8)
|
||||
|
||||
for (row in 0 until 4) {
|
||||
for (x in 0 until 4) {
|
||||
buffer.put(0x80.toByte())
|
||||
buffer.put(0x0.toByte())
|
||||
buffer.put(0x80.toByte())
|
||||
}
|
||||
|
||||
for (x in 0 until 4) {
|
||||
buffer.put(0x0.toByte())
|
||||
buffer.put(0x0.toByte())
|
||||
buffer.put(0x0.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
for (row in 0 until 4) {
|
||||
for (x in 0 until 4) {
|
||||
buffer.put(0x0.toByte())
|
||||
buffer.put(0x0.toByte())
|
||||
buffer.put(0x0.toByte())
|
||||
}
|
||||
|
||||
for (x in 0 until 4) {
|
||||
buffer.put(0x80.toByte())
|
||||
buffer.put(0x0.toByte())
|
||||
buffer.put(0x80.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
buffer.position()
|
||||
missingTexture.upload(GL_RGB, GL_UNSIGNED_BYTE, buffer)
|
||||
}
|
||||
|
||||
fun setViewport(x: Int, y: Int, width: Int, height: Int) {
|
||||
@ -454,7 +468,6 @@ class StarboundClient : Closeable {
|
||||
}
|
||||
|
||||
fun isSameThread() = thread === Thread.currentThread()
|
||||
fun newTexture(name: String = "<unknown>") = GLTexture2D(name)
|
||||
|
||||
fun loadTexture(path: String): GLTexture2D {
|
||||
ensureSameThread()
|
||||
@ -464,13 +477,27 @@ class StarboundClient : Closeable {
|
||||
val data = Image.get(it)
|
||||
|
||||
if (data == null) {
|
||||
LOGGER.error("Texture {} is missing! Falling back to {}", it, missingTexturePath)
|
||||
LOGGER.error("Texture $it is not found, falling back to missing texture")
|
||||
missingTexture
|
||||
} else {
|
||||
newTexture(it).upload(data).also {
|
||||
it.textureMinFilter = GL_NEAREST
|
||||
it.textureMagFilter = GL_NEAREST
|
||||
}
|
||||
val tex = GLTexture2D(data.width, data.height, when (data.amountOfChannels) {
|
||||
1 -> GL_R8
|
||||
3 -> GL_RGB8
|
||||
4 -> GL_RGBA8
|
||||
else -> throw IllegalArgumentException("Unknown amount of channels in $it: ${data.amountOfChannels}")
|
||||
})
|
||||
|
||||
tex.upload(when (data.amountOfChannels) {
|
||||
1 -> GL_RED
|
||||
3 -> GL_RGB
|
||||
4 -> GL_RGBA
|
||||
else -> throw IllegalArgumentException("Unknown amount of channels in $it: ${data.amountOfChannels}")
|
||||
}, GL_UNSIGNED_BYTE, data.data)
|
||||
|
||||
tex.textureMinFilter = GL_NEAREST
|
||||
tex.textureMagFilter = GL_NEAREST
|
||||
|
||||
tex
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -489,7 +516,7 @@ class StarboundClient : Closeable {
|
||||
|
||||
programs.position.use()
|
||||
programs.position.colorMultiplier = color
|
||||
programs.position.worldMatrix = stack.last()
|
||||
programs.position.modelMatrix = stack.last()
|
||||
|
||||
builder.draw(GL_LINES)
|
||||
}
|
||||
@ -632,7 +659,9 @@ class StarboundClient : Closeable {
|
||||
var viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
|
||||
private set
|
||||
|
||||
val viewportLightingTexture = newTexture("Viewport Lighting")
|
||||
var viewportLightingTexture = GLTexture2D(1, 1, GL_RGB8)
|
||||
private set
|
||||
|
||||
private var viewportLightingMem: ByteBuffer? = null
|
||||
|
||||
fun updateViewportParams() {
|
||||
@ -643,8 +672,14 @@ class StarboundClient : Closeable {
|
||||
|
||||
viewportCellX = roundTowardsNegativeInfinity(viewportRectangle.mins.x) - 16
|
||||
viewportCellY = roundTowardsNegativeInfinity(viewportRectangle.mins.y) - 16
|
||||
viewportCellWidth = roundTowardsPositiveInfinity(viewportRectangle.width) + 32
|
||||
viewportCellHeight = roundTowardsPositiveInfinity(viewportRectangle.height) + 32
|
||||
val viewportCellWidth0 = roundTowardsPositiveInfinity(viewportRectangle.width) + 32
|
||||
val viewportCellHeight0 = roundTowardsPositiveInfinity(viewportRectangle.height) + 32
|
||||
|
||||
if ((viewportCellWidth0 - viewportCellWidth).absoluteValue > 1)
|
||||
viewportCellWidth = viewportCellWidth0
|
||||
|
||||
if ((viewportCellHeight0 - viewportCellHeight).absoluteValue > 1)
|
||||
viewportCellHeight = viewportCellHeight0
|
||||
|
||||
viewportTopRight = screenToWorld(viewportWidth, viewportHeight)
|
||||
viewportBottomLeft = screenToWorld(0, 0)
|
||||
@ -655,6 +690,7 @@ class StarboundClient : Closeable {
|
||||
|
||||
if (viewportCellWidth > 0 && viewportCellHeight > 0) {
|
||||
viewportLightingMem = ByteBuffer.allocateDirect(viewportCellWidth.coerceAtMost(4096) * viewportCellHeight.coerceAtMost(4096) * 3)
|
||||
viewportLightingTexture = GLTexture2D(viewportCellWidth.coerceAtMost(4096), viewportCellHeight.coerceAtMost(4096), GL_RGB8)
|
||||
} else {
|
||||
viewportLightingMem = null
|
||||
}
|
||||
@ -681,6 +717,8 @@ class StarboundClient : Closeable {
|
||||
onPostDrawWorldOnce.add(lambda)
|
||||
}
|
||||
|
||||
private val layers = LayeredRenderer()
|
||||
|
||||
fun renderFrame(): Boolean {
|
||||
ensureSameThread()
|
||||
|
||||
@ -711,14 +749,14 @@ class StarboundClient : Closeable {
|
||||
cleanup()
|
||||
GLFW.glfwPollEvents()
|
||||
|
||||
if (world != null) {
|
||||
if (Starbound.initialized)
|
||||
world.think()
|
||||
}
|
||||
if (world != null && Starbound.initialized)
|
||||
world.think()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
layers.clear()
|
||||
|
||||
uberShaderPrograms.forEachValid {
|
||||
if (it.flags.contains(UberShader.Flag.NEEDS_SCREEN_SIZE)) {
|
||||
it.screenSize = Vector2f(viewportWidth.toFloat(), viewportHeight.toFloat())
|
||||
@ -727,7 +765,6 @@ class StarboundClient : Closeable {
|
||||
|
||||
if (world != null) {
|
||||
updateViewportParams()
|
||||
val layers = LayeredRenderer()
|
||||
|
||||
if (Starbound.initialized)
|
||||
world.think()
|
||||
@ -741,7 +778,8 @@ class StarboundClient : Closeable {
|
||||
.scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
|
||||
.translate(-camera.pos.x.toFloat(), -camera.pos.y.toFloat()) // перемещаем вид к камере
|
||||
|
||||
regularShaderPrograms.forEachValid { it.viewMatrix = viewMatrix }
|
||||
uberShaderPrograms.forEachValid { it.viewMatrix = viewMatrix }
|
||||
fontShaderPrograms.forEachValid { it.viewMatrix = viewMatrix }
|
||||
|
||||
for (lambda in onPreDrawWorld) {
|
||||
lambda.invoke(layers)
|
||||
@ -763,16 +801,13 @@ class StarboundClient : Closeable {
|
||||
viewportLightingMem.position(0)
|
||||
BufferUtils.zeroBuffer(viewportLightingMem)
|
||||
viewportLightingMem.position(0)
|
||||
viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096))
|
||||
viewportLighting.calculate(viewportLightingMem, viewportLightingTexture.width, viewportLightingTexture.height)
|
||||
viewportLightingMem.position(0)
|
||||
|
||||
val old = textureUnpackAlignment
|
||||
textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1
|
||||
textureUnpackAlignment = if (viewportLightingTexture.width.coerceAtMost(4096) % 4 == 0) 4 else 1
|
||||
|
||||
viewportLightingTexture.upload(
|
||||
GL_RGB,
|
||||
viewportLighting.width.coerceAtMost(4096),
|
||||
viewportLighting.height.coerceAtMost(4096),
|
||||
GL_RGB,
|
||||
GL_UNSIGNED_BYTE,
|
||||
viewportLightingMem
|
||||
@ -780,14 +815,11 @@ class StarboundClient : Closeable {
|
||||
|
||||
textureUnpackAlignment = old
|
||||
} else {
|
||||
viewportLightingTexture.upload(GL_RGBA, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, WHITE)
|
||||
viewportLightingTexture = whiteTexture
|
||||
}
|
||||
|
||||
viewportLightingTexture.textureMinFilter = GL_LINEAR
|
||||
|
||||
activeTexture = lightMapLocation
|
||||
texture2D = viewportLightingTexture
|
||||
activeTexture = 0
|
||||
textures2D[lightMapLocation] = viewportLightingTexture
|
||||
|
||||
val lightmapUV = if (fullbright) Vector4f.ZERO else Vector4f(
|
||||
((viewportBottomLeft.x - viewportCellX) / viewportLighting.width).toFloat(),
|
||||
@ -809,7 +841,8 @@ class StarboundClient : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
regularShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
|
||||
uberShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
|
||||
fontShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
|
||||
|
||||
val thisTime = System.currentTimeMillis()
|
||||
|
||||
@ -847,7 +880,7 @@ class StarboundClient : Closeable {
|
||||
font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f)
|
||||
font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f)
|
||||
font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f)
|
||||
font.render("OGL A: ${objectsCreated - objectsCleaned} D: $objectsCleaned", y = font.lineHeight * 1.8f, scale = 0.4f)
|
||||
font.render("OGL C: $objectsCreated D: $objectsCleaned A: ${objectsCreated - objectsCleaned}", y = font.lineHeight * 1.8f, scale = 0.4f)
|
||||
|
||||
GLFW.glfwSwapBuffers(window)
|
||||
GLFW.glfwPollEvents()
|
||||
|
@ -9,10 +9,12 @@ sealed class BufferObject : GLObject() {
|
||||
final override val client = StarboundClient.current()
|
||||
abstract val glType: Int
|
||||
|
||||
@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
|
||||
override fun bind() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
|
||||
override fun unbind() {
|
||||
// do nothing
|
||||
}
|
||||
|
@ -33,8 +33,7 @@ class GLFrameBuffer : GLObject() {
|
||||
throw IllegalStateException("Already has texture attached")
|
||||
}
|
||||
|
||||
val texture = GLTexture2D("framebuffer_$pointer")
|
||||
texture.allocate(format, width, height)
|
||||
val texture = GLTexture2D(width, height, format)
|
||||
|
||||
val old = client.framebuffer
|
||||
bind()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.client.gl
|
||||
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.lwjgl.opengl.GL11
|
||||
import org.lwjgl.opengl.GL45.*
|
||||
import org.lwjgl.stb.STBImage
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
@ -30,24 +31,23 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value:
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
class GLTexture2D(val name: String = "<unknown>") : GLObject() {
|
||||
class GLTexture2D(val width: Int, val height: Int, val format: Int, val levels: Int = 1) : GLObject() {
|
||||
init {
|
||||
require(width > 0 && height > 0) { "Invalid image size: $width x $height" }
|
||||
require(levels > 0) { "Invalid image level count: $levels" }
|
||||
}
|
||||
|
||||
override val client = StarboundClient.current()
|
||||
override val pointer = glCreateTextures(GL_TEXTURE_2D)
|
||||
|
||||
init {
|
||||
checkForGLError()
|
||||
checkForGLError("Creating texture")
|
||||
client.registerCleanable(this, ::glDeleteTextures, pointer)
|
||||
|
||||
glTextureStorage2D(pointer, levels, format, width, height)
|
||||
checkForGLError("Invalid image format: $format")
|
||||
}
|
||||
|
||||
var width = 0
|
||||
private set
|
||||
|
||||
var height = 0
|
||||
private set
|
||||
|
||||
var uploaded = false
|
||||
private set
|
||||
|
||||
val aspectRatioWH: Float get() {
|
||||
if (height == 0) {
|
||||
return 1f
|
||||
@ -71,13 +71,14 @@ class GLTexture2D(val name: String = "<unknown>") : GLObject() {
|
||||
var textureWrapS by GLTexturePropertyTracker(GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||
var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT)
|
||||
|
||||
@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
|
||||
override fun bind() {
|
||||
client.texture2D = this
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
|
||||
override fun unbind() {
|
||||
if (client.texture2D === this)
|
||||
client.texture2D = null
|
||||
// do nothing
|
||||
}
|
||||
|
||||
fun generateMips(): GLTexture2D {
|
||||
@ -88,186 +89,31 @@ class GLTexture2D(val name: String = "<unknown>") : GLObject() {
|
||||
}
|
||||
|
||||
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 upload(level: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
||||
return upload(level, 0, 0, width, height, bufferFormat, dataFormat, data)
|
||||
}
|
||||
|
||||
fun allocate(loadedFormat: Int, width: Int, height: Int): GLTexture2D {
|
||||
return allocate(0, loadedFormat, width, height)
|
||||
fun upload(bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
||||
return upload(0, 0, 0, width, height, bufferFormat, dataFormat, data)
|
||||
}
|
||||
|
||||
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 {
|
||||
client.ensureSameThread()
|
||||
|
||||
if (!path.exists()) {
|
||||
throw FileNotFoundException("${path.absolutePath} does not exist")
|
||||
fun upload(level: Int, x: Int, y: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
||||
require(x >= 0 && y >= 0 && width > 0 && height > 0 && x + width <= this.width && y + height <= this.height) {
|
||||
"Bad texture rectangle specified: $x x $y -> ${x + width} x ${y + height}, which is out of bounds for texture ${this.width} x ${this.height}"
|
||||
}
|
||||
|
||||
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 {
|
||||
client.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 {
|
||||
client.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 {
|
||||
client.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 {
|
||||
client.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)
|
||||
|
||||
require(level in 0 until levels) { "Level out of bounds: $level" }
|
||||
glTextureSubImage2D(pointer, level, x, y, width, height, bufferFormat, dataFormat, data)
|
||||
checkForGLError("Uploading texture data")
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
package ru.dbotthepony.kstarbound.client.gl.properties
|
||||
|
||||
import org.lwjgl.opengl.GL45
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLObject
|
||||
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||
|
||||
class GLTexturesTracker<T : GLObject>(maxValue: Int) {
|
||||
private val client = StarboundClient.current()
|
||||
private val values = arrayOfNulls<GLObject>(maxValue)
|
||||
|
||||
operator fun get(index: Int): T? {
|
||||
return values[index] as T?
|
||||
}
|
||||
|
||||
operator fun set(index: Int, value: T?) {
|
||||
client.ensureSameThread()
|
||||
require(value == null || client === value.client) { "$value does not belong to $client" }
|
||||
|
||||
if (values[index] === value)
|
||||
return
|
||||
|
||||
if (value == null) {
|
||||
GL45.glBindTextureUnit(index, 0)
|
||||
checkForGLError()
|
||||
} else {
|
||||
GL45.glBindTextureUnit(index, value.pointer)
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
values[index] = value
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.client.gl.properties
|
||||
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_2D
|
||||
import org.lwjgl.opengl.GL11.glBindTexture
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
||||
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class TexturesTracker(maxValue: Int) : ReadWriteProperty<StarboundClient, GLTexture2D?> {
|
||||
private val values = arrayOfNulls<GLTexture2D>(maxValue)
|
||||
|
||||
override fun getValue(thisRef: StarboundClient, property: KProperty<*>): GLTexture2D? {
|
||||
return values[thisRef.activeTexture]
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: StarboundClient, property: KProperty<*>, value: GLTexture2D?) {
|
||||
thisRef.ensureSameThread()
|
||||
|
||||
require(value == null || thisRef === value.client) { "$value does not belong to $thisRef" }
|
||||
|
||||
if (values[thisRef.activeTexture] === value) {
|
||||
return
|
||||
}
|
||||
|
||||
values[thisRef.activeTexture] = value
|
||||
|
||||
if (value == null) {
|
||||
glBindTexture(GL_TEXTURE_2D, 0)
|
||||
checkForGLError()
|
||||
return
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, value.pointer)
|
||||
checkForGLError()
|
||||
}
|
||||
}
|
@ -28,13 +28,6 @@ open class GLShaderProgram(
|
||||
shaders: Iterable<GLShader>,
|
||||
val attributes: VertexAttributes
|
||||
) : GLObject() {
|
||||
interface Regular {
|
||||
var viewMatrix: Matrix3f // set before rendering anything (camera and/or projection)
|
||||
var worldMatrix: Matrix3f // global matrix stack
|
||||
var modelMatrix: Matrix3f // should be set by drawing code itself
|
||||
var colorMultiplier: IStruct4f
|
||||
}
|
||||
|
||||
final override val client = StarboundClient.current()
|
||||
final override val pointer = glCreateProgram()
|
||||
|
||||
|
@ -22,38 +22,10 @@ private fun shaders1(name: String): List<GLShader> {
|
||||
return listOf(client.internalVertex("shaders/vertex/$name.vsh"), client.internalFragment("shaders/fragment/$name.fsh"))
|
||||
}
|
||||
|
||||
private fun fontShaders(): List<GLShader> {
|
||||
val client = StarboundClient.current()
|
||||
var read = StarboundClient.readInternal("shaders/fragment/font.fsh")
|
||||
|
||||
val textures = ArrayList<String>()
|
||||
val cases = ArrayList<String>()
|
||||
val cases2 = ArrayList<String>()
|
||||
|
||||
for (i in 0 until client.maxUserTextureBlocks) {
|
||||
cases.add("\tvec4 colorResult$i = colorMultiplier * texture(texture$i, uvOut).r;")
|
||||
}
|
||||
|
||||
for (i in 0 until client.maxUserTextureBlocks) {
|
||||
textures.add("layout (binding=$i) uniform sampler2D texture$i;")
|
||||
cases2.add("if (textureIndexOut < ${i + 0.1f}) { colorResult = colorResult$i; }")
|
||||
}
|
||||
|
||||
cases2.add(" { colorResult = colorMultiplier; }")
|
||||
|
||||
read = read.replace("uniform sampler2D texture0;", textures.joinToString("\n")).replace("\tcolorResult = colorMultiplier * texture(texture0, uvOut).r;", cases.joinToString("\n") + cases2.joinToString("\n\telse "))
|
||||
|
||||
val fragment = GLShader(read, GL_FRAGMENT_SHADER)
|
||||
val vertex = GLShader(StarboundClient.readInternal("shaders/vertex/font.vsh"), GL_VERTEX_SHADER)
|
||||
|
||||
return listOf(fragment, vertex)
|
||||
}
|
||||
|
||||
class FontProgram : GLShaderProgram(shaders1("font"), FONT_VERTEX_FORMAT), GLShaderProgram.Regular {
|
||||
override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
|
||||
override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix")
|
||||
override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix", true)
|
||||
override var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
|
||||
class FontProgram : GLShaderProgram(shaders1("font"), FONT_VERTEX_FORMAT) {
|
||||
var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
|
||||
var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix")
|
||||
var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
|
||||
|
||||
var texture by IUniform("texture0")
|
||||
|
||||
|
@ -2,33 +2,37 @@ package ru.dbotthepony.kstarbound.client.gl.shader
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||
import org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER
|
||||
import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributeType
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
|
||||
import ru.dbotthepony.kstarbound.client.render.RenderConfig
|
||||
import ru.dbotthepony.kvector.api.IStruct4f
|
||||
import ru.dbotthepony.kvector.arrays.Matrix3f
|
||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||
import java.util.Collections
|
||||
import java.util.EnumSet
|
||||
import java.util.function.Function
|
||||
|
||||
class UberShader private constructor(
|
||||
fragment: GLShader,
|
||||
vertex: GLShader,
|
||||
attributes: VertexAttributes,
|
||||
val flags: Set<Flag>,
|
||||
) : GLShaderProgram(listOf(fragment, vertex), attributes), GLShaderProgram.Regular {
|
||||
) : GLShaderProgram(listOf(fragment, vertex), attributes) {
|
||||
enum class Flag {
|
||||
LIGHT_MAP,
|
||||
NEEDS_SCREEN_SIZE;
|
||||
}
|
||||
|
||||
override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
|
||||
override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix")
|
||||
override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix")
|
||||
override var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
|
||||
var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
|
||||
var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix")
|
||||
var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
|
||||
|
||||
var texture0 by IUniform("texture0", VertexAttributeType.UV !in attributes)
|
||||
|
||||
@ -40,11 +44,24 @@ class UberShader private constructor(
|
||||
|
||||
init {
|
||||
viewMatrix = Matrix3f.identity()
|
||||
worldMatrix = Matrix3f.identity()
|
||||
modelMatrix = Matrix3f.identity()
|
||||
colorMultiplier = RGBAColor.WHITE
|
||||
}
|
||||
|
||||
private val textureConfigs = Reference2ObjectOpenHashMap<GLTexture2D, RenderConfig<UberShader>>()
|
||||
|
||||
fun config(texture: GLTexture2D): RenderConfig<UberShader> {
|
||||
return textureConfigs.computeIfAbsent(texture, Reference2ObjectFunction {
|
||||
object : RenderConfig<UberShader>(this) {
|
||||
override fun setup() {
|
||||
super.setup()
|
||||
|
||||
client.textures2D[0] = texture
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private val directives = Object2ObjectArrayMap<String, String>()
|
||||
private val attributes = ObjectArraySet<VertexAttributeType>()
|
||||
|
@ -12,7 +12,7 @@ import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||
class StreamVertexBuilder(
|
||||
attributes: VertexAttributes,
|
||||
type: GeometryType? = null,
|
||||
initialCapacity: Int = 64,
|
||||
initialCapacity: Int = 32,
|
||||
) {
|
||||
val state = StarboundClient.current()
|
||||
val builder = VertexBuilder(attributes, type, initialCapacity)
|
||||
|
@ -51,7 +51,7 @@ private fun indexSize(type: Int): Int {
|
||||
class VertexBuilder(
|
||||
val attributes: VertexAttributes,
|
||||
val defaultMode: GeometryType? = null,
|
||||
initialCapacity: Int = 64
|
||||
initialCapacity: Int = 32
|
||||
) {
|
||||
constructor(attributes: VertexAttributes, initialCapacity: Int) : this(attributes, null, initialCapacity)
|
||||
|
||||
@ -88,6 +88,14 @@ class VertexBuilder(
|
||||
var elementCount = 0 // GeometryType specific (quads, triangles, lines), less than or equal (points) to vertexCount
|
||||
private set
|
||||
|
||||
fun isEmpty(): Boolean {
|
||||
return !inVertex && vertexCount == 0
|
||||
}
|
||||
|
||||
fun isNotEmpty(): Boolean {
|
||||
return inVertex || vertexCount != 0
|
||||
}
|
||||
|
||||
private fun ensureCapacity() {
|
||||
val mode = mode ?: return
|
||||
|
||||
@ -177,8 +185,12 @@ class VertexBuilder(
|
||||
}
|
||||
|
||||
check(elementVertices == 0) { "Can't change buffer geometry type while not having fully built current element" }
|
||||
this.mode = mode
|
||||
ensureCapacity()
|
||||
|
||||
if (this.mode !== mode) {
|
||||
this.mode = mode
|
||||
ensureCapacity()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -43,8 +43,7 @@ class Box2DRenderer : IDebugDraw {
|
||||
|
||||
state.programs.position.use()
|
||||
state.programs.position.colorMultiplier = color
|
||||
state.programs.position.worldMatrix = state.stack.last()
|
||||
state.programs.position.modelMatrix = identity
|
||||
state.programs.position.modelMatrix = state.stack.last()
|
||||
|
||||
builder.draw(GL_LINES)
|
||||
}
|
||||
@ -68,8 +67,7 @@ class Box2DRenderer : IDebugDraw {
|
||||
|
||||
state.programs.position.use()
|
||||
state.programs.position.colorMultiplier = color
|
||||
state.programs.position.worldMatrix = state.stack.last()
|
||||
state.programs.position.modelMatrix = identity
|
||||
state.programs.position.modelMatrix = state.stack.last()
|
||||
|
||||
builder.draw(GL_TRIANGLES)
|
||||
}
|
||||
|
@ -98,16 +98,13 @@ class Font(
|
||||
Vector2i(roundTowardsPositiveInfinity(face.nativeMemory.bbox.xMax.toInt() / (12.0 * 48.0 / size)), roundTowardsPositiveInfinity(face.nativeMemory.bbox.yMax.toInt() / (12.0 * 48.0 / size))),
|
||||
)
|
||||
|
||||
private val atlas = GLTexture2D()
|
||||
private val atlasWidth: Int = glGetInternalformati(GL_TEXTURE_2D, GL_RED, GL_MAX_WIDTH).coerceAtMost(4096)
|
||||
private val atlasHeight: Int = glGetInternalformati(GL_TEXTURE_2D, GL_RED, GL_MAX_HEIGHT).coerceAtMost(4096)
|
||||
private var nextAtlasX = 0
|
||||
private var nextAtlasY = 0
|
||||
private val atlas = GLTexture2D(atlasWidth, atlasHeight, GL_R8)
|
||||
|
||||
init {
|
||||
glTextureStorage2D(atlas.pointer, 1, GL_R8, atlasWidth, atlasHeight)
|
||||
checkForGLError()
|
||||
|
||||
atlas.textureMinFilter = GL_LINEAR
|
||||
atlas.textureMagFilter = GL_LINEAR
|
||||
atlas.textureWrapS = GL_CLAMP_TO_EDGE
|
||||
@ -217,12 +214,11 @@ class Font(
|
||||
if (builder.elementCount != 0) {
|
||||
client.programs.font.use()
|
||||
client.programs.font.colorMultiplier = color
|
||||
client.programs.font.worldMatrix = client.stack.last()
|
||||
client.programs.font.modelMatrix = client.stack.last()
|
||||
|
||||
builder.upload(vbo, ebo, GL_STREAM_DRAW)
|
||||
|
||||
client.activeTexture = 0
|
||||
atlas.bind()
|
||||
client.textures2D[0] = atlas
|
||||
|
||||
client.vao = vao
|
||||
glDrawElements(GL_TRIANGLES, builder.indexCount, builder.indexType, 0L)
|
||||
@ -341,8 +337,7 @@ class Font(
|
||||
try {
|
||||
uv = Vector4f(nextAtlasX / atlasWidth.toFloat(), nextAtlasY / atlasHeight.toFloat(), (nextAtlasX + bitmap.width) / atlasWidth.toFloat(), (nextAtlasY + bitmap.rows) / atlasHeight.toFloat())
|
||||
|
||||
glTextureSubImage2D(atlas.pointer, 0, nextAtlasX, nextAtlasY, bitmap.width, bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, buff)
|
||||
checkForGLError("Uploading glyph texture data")
|
||||
atlas.upload(0, nextAtlasX, nextAtlasY, bitmap.width, bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, buff)
|
||||
|
||||
nextAtlasX += bbox.width
|
||||
|
||||
|
@ -1,28 +1,116 @@
|
||||
package ru.dbotthepony.kstarbound.client.render
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import ru.dbotthepony.kvector.arrays.Matrix3fStack
|
||||
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||
import java.util.function.Function
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* Позволяет вызывать отрисовщики в определённой (послойной) последовательности
|
||||
*/
|
||||
class LayeredRenderer {
|
||||
private val layers = Long2ObjectAVLTreeMap<ArrayList<() -> Unit>>()
|
||||
interface IGeometryLayer {
|
||||
fun add(renderer: () -> Unit)
|
||||
fun getBuilder(config: RenderConfig<*>): VertexBuilder
|
||||
fun render()
|
||||
fun clear()
|
||||
fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>>
|
||||
}
|
||||
|
||||
fun add(layer: Long, renderer: () -> Unit) {
|
||||
layers.computeIfAbsent(layer, Function { ArrayList() }).add(renderer)
|
||||
object OneShotGeometryLayer : IGeometryLayer {
|
||||
override fun add(renderer: () -> Unit) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
fun render() {
|
||||
for (list in layers.values) {
|
||||
for (renderer in list) {
|
||||
renderer.invoke()
|
||||
override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun render() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
class LayeredRenderer {
|
||||
class Layer(val layer: RenderLayer.Point) : IGeometryLayer {
|
||||
private val meshes = ArrayList<ConfiguredMesh<*>>()
|
||||
private val callbacks = ArrayList<() -> Unit>()
|
||||
private val builders = Reference2ObjectLinkedOpenHashMap<RenderConfig<*>, StreamVertexBuilder>()
|
||||
|
||||
override fun add(renderer: () -> Unit) {
|
||||
callbacks.add(renderer)
|
||||
}
|
||||
|
||||
override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
|
||||
return builders.computeIfAbsent(config, Function {
|
||||
StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity)
|
||||
}).builder
|
||||
}
|
||||
|
||||
override fun render() {
|
||||
builders.entries.forEach {
|
||||
if (it.value.builder.isNotEmpty()) {
|
||||
it.key.setup()
|
||||
it.value.upload()
|
||||
it.value.draw()
|
||||
it.key.uninstall()
|
||||
}
|
||||
}
|
||||
|
||||
meshes.forEach {
|
||||
it.render()
|
||||
}
|
||||
|
||||
callbacks.forEach {
|
||||
it.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
layers.clear()
|
||||
override fun clear() {
|
||||
builders.values.forEach { it.builder.begin() }
|
||||
meshes.clear()
|
||||
callbacks.clear()
|
||||
}
|
||||
|
||||
override fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
|
||||
if (meshes.isNotEmpty() || callbacks.isNotEmpty())
|
||||
throw IllegalStateException("This layer (index $layer) contains preconfigured meshes and/or callbacks")
|
||||
|
||||
return builders.entries.stream()
|
||||
.filter { it.value.builder.isNotEmpty() }
|
||||
.map { ConfiguredMesh(it.key, Mesh(it.value.builder)) to layer }
|
||||
}
|
||||
}
|
||||
|
||||
private val layers = Object2ObjectAVLTreeMap<RenderLayer.Point, Layer>()
|
||||
|
||||
fun getBuilder(layer: RenderLayer.Point, config: RenderConfig<*>): VertexBuilder {
|
||||
return getLayer(layer).getBuilder(config)
|
||||
}
|
||||
|
||||
fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
|
||||
return layers.values.stream().flatMap { it.bakeIntoMeshes() }
|
||||
}
|
||||
|
||||
fun add(layer: RenderLayer.Point, renderer: () -> Unit) {
|
||||
getLayer(layer).add(renderer)
|
||||
}
|
||||
|
||||
fun getLayer(layer: RenderLayer.Point): Layer {
|
||||
return layers.computeIfAbsent(layer, Function { Layer(it) })
|
||||
}
|
||||
|
||||
fun render() {
|
||||
layers.values.forEach { it.render() }
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
layers.values.forEach { it.clear() }
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,9 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||
import ru.dbotthepony.kvector.arrays.Matrix4f
|
||||
|
||||
/**
|
||||
* mesh is container for paired VAO, VBO and EBO, and metadata for them
|
||||
*/
|
||||
class Mesh() {
|
||||
constructor(builder: VertexBuilder) : this() {
|
||||
load(builder, GL45.GL_STATIC_DRAW)
|
||||
@ -44,6 +47,9 @@ class Mesh() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mesh + RenderConfig
|
||||
*/
|
||||
data class ConfiguredMesh<T : GLShaderProgram>(val config: RenderConfig<T>, val mesh: Mesh = Mesh()) {
|
||||
fun render() {
|
||||
config.setup()
|
||||
|
@ -1,28 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.client.render
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||
import java.util.stream.Stream
|
||||
|
||||
class MultiMeshBuilder {
|
||||
data class Entry(val config: RenderConfig<*>, val builder: VertexBuilder, val layer: Long)
|
||||
|
||||
private val meshes = Reference2ObjectOpenHashMap<RenderConfig<*>, Long2ObjectOpenHashMap<Entry>>()
|
||||
|
||||
fun get(config: RenderConfig<*>, layer: Long): VertexBuilder {
|
||||
return meshes.computeIfAbsent(config, Reference2ObjectFunction {
|
||||
Long2ObjectOpenHashMap()
|
||||
}).computeIfAbsent(layer, Long2ObjectFunction { Entry(config, VertexBuilder(config.program.attributes, config.initialBuilderCapacity), layer) }).builder
|
||||
}
|
||||
|
||||
fun clear() = meshes.clear()
|
||||
fun isEmpty() = meshes.isEmpty()
|
||||
fun isNotEmpty() = meshes.isNotEmpty()
|
||||
|
||||
fun meshes(): Stream<Entry> {
|
||||
return meshes.values.stream().flatMap { it.values.stream() }
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import ru.dbotthepony.kvector.arrays.Matrix4f
|
||||
|
||||
abstract class RenderConfig<out T : GLShaderProgram>(val program: T) {
|
||||
val client get() = program.client
|
||||
open val initialBuilderCapacity: Int get() = 64
|
||||
open val initialBuilderCapacity: Int get() = 32
|
||||
|
||||
open fun setup() {
|
||||
program.use()
|
||||
|
@ -1,81 +1,112 @@
|
||||
package ru.dbotthepony.kstarbound.client.render
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import org.apache.logging.log4j.LogManager
|
||||
|
||||
const val RenderLayerUpperBits = 5
|
||||
const val RenderLayerLowerBits = 32 - RenderLayerUpperBits
|
||||
const val RenderLayerLowerMask = 0.inv() shr RenderLayerUpperBits
|
||||
enum class RenderLayer {
|
||||
BackgroundOverlay,
|
||||
BackgroundTile,
|
||||
BackgroundTileMod,
|
||||
Platform,
|
||||
Plant,
|
||||
PlantDrop,
|
||||
Object,
|
||||
PreviewObject,
|
||||
BackParticle,
|
||||
Vehicle,
|
||||
Effect,
|
||||
Projectile,
|
||||
Monster,
|
||||
Npc,
|
||||
Player,
|
||||
ItemDrop,
|
||||
Liquid,
|
||||
MiddleParticle,
|
||||
ForegroundTile,
|
||||
ForegroundTileMod,
|
||||
ForegroundEntity,
|
||||
ForegroundOverlay,
|
||||
FrontParticle,
|
||||
Overlay;
|
||||
|
||||
enum class RenderLayer(val index: Long) {
|
||||
BackgroundOverlay (1L shl RenderLayerLowerBits),
|
||||
BackgroundTile (2L shl RenderLayerLowerBits),
|
||||
Platform (3L shl RenderLayerLowerBits),
|
||||
Plant (4L shl RenderLayerLowerBits),
|
||||
PlantDrop (5L shl RenderLayerLowerBits),
|
||||
Object (6L shl RenderLayerLowerBits),
|
||||
PreviewObject (7L shl RenderLayerLowerBits),
|
||||
BackParticle (8L shl RenderLayerLowerBits),
|
||||
Vehicle (9L shl RenderLayerLowerBits),
|
||||
Effect (10L shl RenderLayerLowerBits),
|
||||
Projectile (11L shl RenderLayerLowerBits),
|
||||
Monster (12L shl RenderLayerLowerBits),
|
||||
Npc (13L shl RenderLayerLowerBits),
|
||||
Player (14L shl RenderLayerLowerBits),
|
||||
ItemDrop (15L shl RenderLayerLowerBits),
|
||||
Liquid (16L shl RenderLayerLowerBits),
|
||||
MiddleParticle (17L shl RenderLayerLowerBits),
|
||||
ForegroundTile (18L shl RenderLayerLowerBits),
|
||||
ForegroundEntity (19L shl RenderLayerLowerBits),
|
||||
ForegroundOverlay (20L shl RenderLayerLowerBits),
|
||||
FrontParticle (21L shl RenderLayerLowerBits),
|
||||
Overlay (22L shl RenderLayerLowerBits);
|
||||
val base = Point(this)
|
||||
|
||||
fun point(offset: Long = 0L): Point {
|
||||
return if (offset == 0L)
|
||||
base
|
||||
else
|
||||
Point(this, offset)
|
||||
}
|
||||
|
||||
data class Point(val base: RenderLayer, val offset: Long = 0L) : Comparable<Point> {
|
||||
override fun compareTo(other: Point): Int {
|
||||
var cmp = base.compareTo(other.base)
|
||||
if (cmp == 0) cmp = offset.compareTo(other.offset)
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LogManager.getLogger()
|
||||
|
||||
private inline fun perform(value: String, symbol: Char, operator: (Long, Long) -> Long): Long {
|
||||
val split = value.split(symbol)
|
||||
|
||||
if (split.size != 2) {
|
||||
logger.error("Ambiguous render layer string: $value; assuming 0")
|
||||
return 0
|
||||
fun tileLayer(isBackground: Boolean, isModifier: Boolean, offset: Long = 0L): Point {
|
||||
if (isBackground && isModifier) {
|
||||
return BackgroundTileMod.point(offset)
|
||||
} else if (isBackground) {
|
||||
return BackgroundTile.point(offset)
|
||||
} else if (isModifier) {
|
||||
return ForegroundTileMod.point(offset)
|
||||
} else {
|
||||
val enum = entries.find { it.name == split[0] }
|
||||
|
||||
if (enum == null) {
|
||||
logger.error("Unknown render layer: ${split[0]} in $value; assuming 0")
|
||||
return 0
|
||||
}
|
||||
|
||||
val num = split[1].toLongOrNull()
|
||||
|
||||
if (num == null) {
|
||||
logger.error("Invalid render layer string: $value; assuming 0")
|
||||
return 0
|
||||
}
|
||||
|
||||
return operator(enum.index, num)
|
||||
return ForegroundTile.point(offset)
|
||||
}
|
||||
}
|
||||
|
||||
fun parse(value: String): Long {
|
||||
private val logger = LogManager.getLogger()
|
||||
private val lowercase: ImmutableMap<String, RenderLayer>
|
||||
|
||||
init {
|
||||
val builder = ImmutableMap.Builder<String, RenderLayer>()
|
||||
|
||||
for (value in entries) {
|
||||
builder.put(value.name.lowercase(), value)
|
||||
}
|
||||
|
||||
lowercase = builder.build()
|
||||
}
|
||||
|
||||
private fun perform(value: String, symbol: Char): Point {
|
||||
val before = value.substringBefore(symbol).lowercase()
|
||||
val after = value.substring(before.length)
|
||||
|
||||
val enum = lowercase[before]
|
||||
|
||||
if (enum == null) {
|
||||
logger.error("Unknown render layer: $before in $value; assuming BackgroundOverlay")
|
||||
return BackgroundOverlay.base
|
||||
}
|
||||
|
||||
val num = after.toLongOrNull()
|
||||
|
||||
if (num == null) {
|
||||
logger.error("Invalid render layer string: $value; assuming BackgroundOverlay")
|
||||
return BackgroundOverlay.base
|
||||
}
|
||||
|
||||
return enum.point(num)
|
||||
}
|
||||
|
||||
fun parse(value: String): Point {
|
||||
if ('+' in value) {
|
||||
return perform(value, '+', Long::plus)
|
||||
return perform(value, '+')
|
||||
} else if ('-' in value) {
|
||||
return perform(value, '-', Long::minus)
|
||||
} else if ('*' in value) {
|
||||
return perform(value, '*', Long::times)
|
||||
} else if ('/' in value) {
|
||||
return perform(value, '/', Long::div)
|
||||
return perform(value, '-')
|
||||
} else {
|
||||
val enum = entries.find { it.name == value }
|
||||
val enum = lowercase[value.lowercase()]
|
||||
|
||||
if (enum == null) {
|
||||
logger.error("Unknown render layer: $value; assuming 0")
|
||||
return 0
|
||||
logger.error("Unknown render layer: $value; assuming BackgroundOverlay")
|
||||
return BackgroundOverlay.base
|
||||
}
|
||||
|
||||
return enum.index
|
||||
return enum.base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ class TileRenderers(val client: StarboundClient) {
|
||||
private val background = HashMap<GLTexture2D, Config>()
|
||||
private val matCache = HashMap<String, TileRenderer>()
|
||||
private val modCache = HashMap<String, TileRenderer>()
|
||||
private val identity = Matrix3f.identity()
|
||||
|
||||
fun getMaterialRenderer(defName: String): TileRenderer {
|
||||
return matCache.computeIfAbsent(defName) {
|
||||
@ -49,15 +48,13 @@ class TileRenderers(val client: StarboundClient) {
|
||||
|
||||
override fun setup() {
|
||||
super.setup()
|
||||
client.activeTexture = 0
|
||||
client.depthTest = false
|
||||
program.texture0 = 0
|
||||
texture.bind()
|
||||
client.textures2D[0] = texture
|
||||
texture.textureMagFilter = GL_NEAREST
|
||||
texture.textureMinFilter = GL_NEAREST
|
||||
|
||||
program.worldMatrix = client.stack.last()
|
||||
program.modelMatrix = identity
|
||||
program.modelMatrix = client.stack.last()
|
||||
program.colorMultiplier = color
|
||||
}
|
||||
|
||||
@ -97,8 +94,8 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
||||
HALT
|
||||
}
|
||||
|
||||
val state get() = renderers.client
|
||||
val texture = def.renderParameters.texture?.imagePath?.value?.let { state.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }}
|
||||
val client get() = renderers.client
|
||||
val texture = def.renderParameters.texture?.imagePath?.value?.let { client.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }}
|
||||
|
||||
val equalityTester: EqualityRuleTester = when (def) {
|
||||
is TileDefinition -> TileEqualityTester(def)
|
||||
@ -161,29 +158,32 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
||||
self: ITileState,
|
||||
matchPiece: RenderMatch,
|
||||
getter: ITileAccess,
|
||||
meshBuilder: MultiMeshBuilder,
|
||||
meshBuilder: LayeredRenderer,
|
||||
pos: Vector2i,
|
||||
thisBuilder: VertexBuilder,
|
||||
background: Boolean,
|
||||
isBackground: Boolean,
|
||||
isModifier: Boolean,
|
||||
): TestResult {
|
||||
if (matchPiece.test(getter, equalityTester, pos)) {
|
||||
for (renderPiece in matchPiece.pieces) {
|
||||
if (renderPiece.piece.texture != null) {
|
||||
val program = if (background) {
|
||||
renderers.background(state.loadTexture(renderPiece.piece.texture!!))
|
||||
val program = if (isBackground) {
|
||||
renderers.background(client.loadTexture(renderPiece.piece.texture!!))
|
||||
} else {
|
||||
renderers.foreground(state.loadTexture(renderPiece.piece.texture!!))
|
||||
renderers.foreground(client.loadTexture(renderPiece.piece.texture!!))
|
||||
}
|
||||
|
||||
tesselateAt(self, renderPiece.piece, getter, meshBuilder.get(program, def.renderParameters.zLevel).mode(GeometryType.QUADS), pos, renderPiece.offset, isModifier)
|
||||
tesselateAt(
|
||||
self, renderPiece.piece, getter,
|
||||
meshBuilder.getBuilder(RenderLayer.tileLayer(isBackground, isModifier, def.renderParameters.zLevel), program).mode(GeometryType.QUADS),
|
||||
pos, renderPiece.offset, isModifier)
|
||||
} else {
|
||||
tesselateAt(self, renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset, isModifier)
|
||||
}
|
||||
}
|
||||
|
||||
for (subPiece in matchPiece.subMatches) {
|
||||
val matched = tesselatePiece(self, subPiece, getter, meshBuilder, pos, thisBuilder, background, isModifier)
|
||||
val matched = tesselatePiece(self, subPiece, getter, meshBuilder, pos, thisBuilder, isBackground, isModifier)
|
||||
|
||||
if (matched == TestResult.HALT || matched == TestResult.CONTINUE && matchPiece.haltOnSubMatch) {
|
||||
return TestResult.HALT
|
||||
@ -209,18 +209,20 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
||||
*
|
||||
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
|
||||
*/
|
||||
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: MultiMeshBuilder, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
|
||||
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: LayeredRenderer, pos: Vector2i, isBackground: Boolean = false, isModifier: Boolean = false) {
|
||||
if (texture == null) return
|
||||
|
||||
// если у нас нет renderTemplate
|
||||
// то мы просто не можем его отрисовать
|
||||
val template = def.renderTemplate.value ?: return
|
||||
|
||||
val vertexBuilder = meshBuilder.get(if (background) bakedBackgroundProgramState!! else bakedProgramState!!, def.renderParameters.zLevel).mode(GeometryType.QUADS)
|
||||
val vertexBuilder = meshBuilder
|
||||
.getBuilder(RenderLayer.tileLayer(isBackground, isModifier, def.renderParameters.zLevel), if (isBackground) bakedBackgroundProgramState!! else bakedProgramState!!)
|
||||
.mode(GeometryType.QUADS)
|
||||
|
||||
for ((_, matcher) in template.matches) {
|
||||
for (matchPiece in matcher) {
|
||||
val matched = tesselatePiece(self, matchPiece, getter, meshBuilder, pos, vertexBuilder, background, isModifier)
|
||||
val matched = tesselatePiece(self, matchPiece, getter, meshBuilder, pos, vertexBuilder, isBackground, isModifier)
|
||||
|
||||
if (matched == TestResult.HALT) {
|
||||
break
|
||||
|
@ -10,7 +10,6 @@ import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
|
||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.Mesh
|
||||
import ru.dbotthepony.kstarbound.client.render.MultiMeshBuilder
|
||||
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||
@ -72,7 +71,7 @@ class ClientWorld(
|
||||
|
||||
inner class RenderRegion(val x: Int, val y: Int) {
|
||||
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
||||
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Long>>()
|
||||
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, RenderLayer.Point>>()
|
||||
var isDirty = true
|
||||
|
||||
fun bake() {
|
||||
@ -81,7 +80,7 @@ class ClientWorld(
|
||||
|
||||
bakedMeshes.clear()
|
||||
|
||||
val meshes = MultiMeshBuilder()
|
||||
val meshes = LayeredRenderer()
|
||||
|
||||
for (x in 0 until renderRegionWidth) {
|
||||
for (y in 0 until renderRegionHeight) {
|
||||
@ -91,19 +90,23 @@ class ClientWorld(
|
||||
val material = tile.material
|
||||
|
||||
if (!material.isMeta) {
|
||||
client.tileRenderers.getMaterialRenderer(material.materialName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground)
|
||||
client.tileRenderers
|
||||
.getMaterialRenderer(material.materialName)
|
||||
.tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground)
|
||||
}
|
||||
|
||||
val modifier = tile.modifier
|
||||
|
||||
if (modifier != null) {
|
||||
client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground, isModifier = true)
|
||||
client.tileRenderers
|
||||
.getModifierRenderer(modifier.modName)
|
||||
.tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground, isModifier = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((baked, builder, zLevel) in meshes.meshes()) {
|
||||
bakedMeshes.add(ConfiguredMesh(baked, Mesh(builder)) to zLevel)
|
||||
for ((baked, zLevel) in meshes.bakeIntoMeshes()) {
|
||||
bakedMeshes.add(baked to zLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,7 +158,7 @@ class ClientWorld(
|
||||
}
|
||||
|
||||
for ((baked, zLevel) in background.bakedMeshes) {
|
||||
layers.add(zLevel + RenderLayer.BackgroundTile.index) {
|
||||
layers.add(zLevel) {
|
||||
client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
|
||||
baked.render()
|
||||
client.stack.pop()
|
||||
@ -163,7 +166,7 @@ class ClientWorld(
|
||||
}
|
||||
|
||||
for ((baked, zLevel) in foreground.bakedMeshes) {
|
||||
layers.add(zLevel + RenderLayer.ForegroundTile.index) {
|
||||
layers.add(zLevel) {
|
||||
client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
|
||||
baked.render()
|
||||
client.stack.pop()
|
||||
@ -268,23 +271,11 @@ class ClientWorld(
|
||||
|
||||
for (obj in objects) {
|
||||
if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) {
|
||||
//layers.add(RenderLayer.Object.index) {
|
||||
layers.add(obj.orientation?.renderLayer ?: continue) {
|
||||
/*client.quadWireframe {
|
||||
it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat())
|
||||
it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat())
|
||||
it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat() + 1f)
|
||||
it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat() + 1f)
|
||||
}*/
|
||||
val layer = layers.getLayer(obj.orientation?.renderLayer ?: continue)
|
||||
|
||||
obj.drawables.forEach {
|
||||
val (x, y) = obj.imagePosition
|
||||
|
||||
client.stack.push().last()
|
||||
.translate(obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf)
|
||||
it.render(client)
|
||||
client.stack.pop()
|
||||
}
|
||||
obj.drawables.forEach {
|
||||
val (x, y) = obj.imagePosition
|
||||
it.render(client, layer, obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf)
|
||||
}
|
||||
|
||||
obj.addLights(client.viewportLighting, client.viewportCellX, client.viewportCellY)
|
||||
|
@ -11,6 +11,7 @@ import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
|
||||
import ru.dbotthepony.kstarbound.client.render.IGeometryLayer
|
||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||
@ -34,7 +35,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
||||
color: RGBAColor = RGBAColor.WHITE,
|
||||
fullbright: Boolean = false
|
||||
) : Drawable(position, color, fullbright) {
|
||||
override fun render(client: StarboundClient, x: Float, y: Float) {
|
||||
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@ -49,7 +50,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
||||
color: RGBAColor = RGBAColor.WHITE,
|
||||
fullbright: Boolean = false
|
||||
) : Drawable(position, color, fullbright) {
|
||||
override fun render(client: StarboundClient, x: Float, y: Float) {
|
||||
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@ -79,12 +80,12 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
||||
return Image(path, transform.flatMap({ it.copy().scale(-1f, 1f) }, { it.copy(mirrored = !it.mirrored) }), position, color, fullbright)
|
||||
}
|
||||
|
||||
override fun render(client: StarboundClient, x: Float, y: Float) {
|
||||
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
||||
val sprite = path.sprite ?: return
|
||||
val texture = client.loadTexture(path.imagePath.value!!)
|
||||
val program = if (fullbright) client.programs.positionTexture else client.programs.positionTextureLightmap
|
||||
|
||||
val mat = transform.map({ it }, {
|
||||
val mat = transform.map({ it.copy() }, {
|
||||
val mat = Matrix3f.identity()
|
||||
|
||||
it.scale.map({ mat.scale(it / PIXELS_IN_STARBOUND_UNITf, it / PIXELS_IN_STARBOUND_UNITf) }, { mat.scale(it / PIXELS_IN_STARBOUND_UNITf) })
|
||||
@ -105,27 +106,21 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
||||
mat
|
||||
})
|
||||
|
||||
//program.modelMatrix = mat
|
||||
program.modelMatrix = Matrix3f.identity()
|
||||
val builder = layer.getBuilder(program.config(texture))
|
||||
|
||||
program.worldMatrix = client.stack.last()
|
||||
program.use()
|
||||
program.texture0 = 0
|
||||
client.activeTexture = 0
|
||||
texture.bind()
|
||||
mat.preTranslate(x, y)
|
||||
client.stack.last().mulIntoOther(mat)
|
||||
|
||||
program.builder.builder.begin(GeometryType.QUADS)
|
||||
program.builder.builder.vertex(mat, x, y).uv(sprite.u0, sprite.v0)
|
||||
program.builder.builder.vertex(mat, x + sprite.width, y).uv(sprite.u1, sprite.v0)
|
||||
program.builder.builder.vertex(mat, x + sprite.width, y + sprite.height).uv(sprite.u1, sprite.v1)
|
||||
program.builder.builder.vertex(mat, x, y + sprite.height).uv(sprite.u0, sprite.v1)
|
||||
program.builder.upload()
|
||||
program.builder.draw()
|
||||
builder.mode(GeometryType.QUADS)
|
||||
builder.vertex(mat, 0f, 0f).uv(sprite.u0, sprite.v0)
|
||||
builder.vertex(mat, 0f + sprite.width, 0f).uv(sprite.u1, sprite.v0)
|
||||
builder.vertex(mat, 0f + sprite.width, 0f + sprite.height).uv(sprite.u1, sprite.v1)
|
||||
builder.vertex(mat, 0f, 0f + sprite.height).uv(sprite.u0, sprite.v1)
|
||||
}
|
||||
}
|
||||
|
||||
class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) {
|
||||
override fun render(client: StarboundClient, x: Float, y: Float) {}
|
||||
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {}
|
||||
|
||||
override fun flop(): Drawable {
|
||||
return this
|
||||
@ -136,7 +131,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
||||
return this
|
||||
}
|
||||
|
||||
abstract fun render(client: StarboundClient = StarboundClient.current(), x: Float = 0f, y: Float = 0f)
|
||||
abstract fun render(client: StarboundClient = StarboundClient.current(), layer: IGeometryLayer, x: Float = 0f, y: Float = 0f)
|
||||
|
||||
/**
|
||||
* mirror along X axis
|
||||
|
@ -36,7 +36,7 @@ data class ObjectOrientation(
|
||||
val json: JsonObject,
|
||||
val flipImages: Boolean = false,
|
||||
val drawables: ImmutableList<Drawable>,
|
||||
val renderLayer: Long,
|
||||
val renderLayer: RenderLayer.Point,
|
||||
val imagePosition: Vector2f,
|
||||
val frames: Int,
|
||||
val animationCycle: Double,
|
||||
|
@ -476,6 +476,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
||||
clusters.removeIf {
|
||||
val grid = it.grid
|
||||
|
||||
// wait until thread local caches are refreshed
|
||||
if (grid != null) {
|
||||
if (targetMem != null) {
|
||||
for (x in grid.minX - 1 .. grid.maxX) {
|
||||
|
@ -3,7 +3,7 @@ uniform vec4 colorMultiplier;
|
||||
out vec4 colorResult;
|
||||
|
||||
#ifdef TEXTURE
|
||||
uniform sampler2D texture0;
|
||||
layout (binding = 0) uniform sampler2D texture0;
|
||||
in vec2 uvOut;
|
||||
#endif
|
||||
|
||||
|
@ -17,11 +17,10 @@ out float hueShiftOut;
|
||||
#endif
|
||||
|
||||
uniform mat3 viewMatrix; // projection (viewport) + camera
|
||||
uniform mat3 worldMatrix; // matrix stack
|
||||
uniform mat3 modelMatrix; // local transformations
|
||||
|
||||
void main() {
|
||||
vec3 result = viewMatrix * worldMatrix * modelMatrix * pos;
|
||||
vec3 result = viewMatrix * modelMatrix * pos;
|
||||
gl_Position = vec4(result.x, result.y, 0.0, result.z);
|
||||
|
||||
#ifdef HUE_SHIFT
|
||||
|
@ -5,12 +5,12 @@ layout (location = 0) in vec3 pos;
|
||||
layout (location = 1) in vec2 uvData;
|
||||
|
||||
uniform mat3 viewMatrix; // projection (viewport)
|
||||
uniform mat3 worldMatrix; // world position
|
||||
uniform mat3 modelMatrix; // world position
|
||||
|
||||
out vec2 uvOut;
|
||||
|
||||
void main() {
|
||||
vec3 result = viewMatrix * worldMatrix * pos;
|
||||
vec3 result = viewMatrix * modelMatrix * pos;
|
||||
gl_Position = vec4(result.x, result.y, 0.0, result.z);
|
||||
|
||||
uvOut = uvData;
|
||||
|
Loading…
Reference in New Issue
Block a user