commonMain.casperix.opengl.renderer.OpenGlRenderer2d.kt Maven / Gradle / Ivy
package casperix.opengl.renderer
import casperix.math.axis_aligned.int32.Box2i
import casperix.math.axis_aligned.int32.Dimension2i
import casperix.math.geometry.Quad2f
import casperix.math.geometry.Triangle2f
import casperix.math.quad_matrix.float32.Matrix3f
import casperix.math.vector.float32.Vector3f
import casperix.opengl.*
import casperix.opengl.misc.GLErrorPrinter
import casperix.opengl.renderer.impl.DeviceGraphicData
import casperix.opengl.renderer.impl.GraphicDataProvider
import casperix.opengl.renderer.impl.RenderState
import casperix.opengl.renderer.shader.ShaderController
import casperix.renderer.Environment
import casperix.renderer.Renderer2D
import casperix.renderer.material.Material
import casperix.renderer.vector.VectorGraphic
import casperix.renderer.vector.builder.VectorBuilder
@ExperimentalUnsignedTypes
class OpenGlRenderer2d : Renderer2D {
override var environment = Environment()
set(value) {
flush()
field = value
}
override var viewPort: Dimension2i = Dimension2i.ZERO
set(view) {
flush()
glViewport(0, 0, view.width, view.height)
field = view
}
val statistic = OpenGlRendererStatistic()
val config = OpenGlRendererConfig()
private var lastRenderState: RenderState? = null
private var scissorArea: Box2i? = null
private val shaderProvider = ShaderProvider()
private val stateController = StateController(config.textureConfig)
private val graphicProvider = GraphicDataProvider(stateController, config.cacheConfig.staticConfig)
fun begin() {
validate()
statistic.current.dynamicBufferAmount = graphicProvider.getDynamicBufferAmount()
statistic.current.staticBufferAmount = graphicProvider.getStaticBufferAmount()
statistic.nextFrame()
glEnable(GL_BLEND)
glFrontFace(GL_CCW)
if (config.cullFace) {
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
} else {
glDisable(GL_CULL_FACE)
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
validate()
}
fun end() {
validate()
flush()
stateController.setShader(null)
stateController.setGeometry(null)
glDisable(GL_BLEND)
glActiveTexture(GL_TEXTURE0)
glDisable(GL_CULL_FACE)
validate()
}
override fun flush() {
lastRenderState?.let {
drawBuffer(it)
}
lastRenderState = null
}
override fun clear() {
flush()
environment.backgroundColor.toColor4f().apply {
glClearColor(red, green, blue, alpha)
}
glClear(GL_COLOR_BUFFER_BIT)
}
override fun getScissor(): Box2i? {
return scissorArea
}
override fun setScissor(area: Box2i?) {
if (this.scissorArea == area) return
flush()
if (area == null) {
scissorArea = null
glDisable(GL_SCISSOR_TEST)
} else {
val clamped = area//Box2i(area.min.clamp(Vector2i.ZERO, viewportMax), area.max.lower(viewportMax))
glScissor(clamped.min.x, viewPort.height - clamped.max.y - 1, clamped.dimension.x, clamped.dimension.y)
glEnable(GL_SCISSOR_TEST)
scissorArea = clamped
}
}
override fun drawQuad(material: Material, quad: Quad2f) {
val geometry = if (material.albedoMap != null) {
VectorBuilder(hasPosition2 = true, hasTextureCoord = true).buildGeometry {
addQuad(quad, quad)
}
} else {
VectorBuilder(hasPosition2 = true).buildGeometry {
addQuad(quad)
}
}
drawGraphic(VectorGraphic(material, geometry))
}
override fun drawTriangle(material: Material, triangle: Triangle2f) {
val geometry = if (material.albedoMap != null) {
VectorBuilder(hasPosition2 = true, hasTextureCoord = true).buildGeometry {
addTriangle(triangle, triangle)
}
} else {
VectorBuilder(hasPosition2 = true).buildGeometry {
addTriangle(triangle)
}
}
drawGraphic(VectorGraphic(material, geometry))
}
override fun drawGraphic(graphic: VectorGraphic, modelTransform: Matrix3f) {
if (graphic.geometry.vertices.size == 0) {
return
}
val data = graphicProvider.get(stateController, graphic, statistic.frameIndex) ?: return
val controller = shaderProvider.getOrCreate(data.attributes) ?: return
pushToBuffer(modelTransform, controller, data)
}
private fun pushToBuffer(nextTransform: Matrix3f, nextController: ShaderController, nextData: DeviceGraphicData) {
val nextGraphic = nextData.graphic
val nextGeometry = nextGraphic.geometry
val nextMaterial = nextGraphic.material
val nextBuffer = nextData.buffer
val nextRenderState = RenderState(nextTransform, nextTransform * environment.viewMatrix * environment.projectionMatrix, nextController, nextMaterial, nextBuffer)
val useBatch = config.cacheConfig.accumulateBatches
val last = lastRenderState
val isStateChanged = lastRenderState != nextRenderState
val isStaticLastOrNext = last?.buffer?.isStatic == true || nextBuffer.isStatic
val isBufferLimitReached =
nextGeometry.indices.size + nextBuffer.data.indicesAmount > config.cacheConfig.maxDrawBufferIndices
if (isStateChanged || isStaticLastOrNext || isBufferLimitReached) {
if (last != null) {
drawBuffer(last)
}
}
if (!nextBuffer.isStatic) {
nextBuffer.appendData(nextGeometry.vertices.data, nextGeometry.indices)
}
if (isStateChanged || !useBatch) {
statistic.current.states++
lastRenderState = nextRenderState
if (!useBatch) {
drawBuffer(nextRenderState)
}
}
}
private fun drawBuffer(renderState: RenderState) {
val buffer = renderState.buffer
if (buffer.isStatic) {
statistic.current.static++
} else {
buffer.uploadData(stateController)
}
stateController.setShader(renderState.controller)
stateController.setMaterial(renderState.material)
stateController.setGeometry(renderState.buffer.data)
stateController.setTransform(renderState.model, renderState.projectionViewModel)
stateController.setLightPos(environment.lightPosition)
buffer.draw()
statistic.current.triangles += buffer.data.indicesAmount / 3
statistic.current.batches++
if (!buffer.isStatic) {
buffer.reset()
}
validate()
}
private fun Vector3f.flipY():Vector3f {
return Vector3f(x, viewPort.height - y, z)
}
private fun validate() {
GLErrorPrinter.throwIfExist()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy