All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cwinter.codecraft.graphics.engine.RenderFrame.scala Maven / Gradle / Ivy

package cwinter.codecraft.graphics.engine

import java.awt.{Font, TextField}
import com.jogamp.opengl.GL._
import com.jogamp.opengl._

import com.jogamp.opengl.util.awt.TextRenderer
import cwinter.codecraft.graphics.materials.Material
import cwinter.codecraft.graphics.model.{TheCompositeModelBuilderCache, TheModelCache, VBO}
import cwinter.codecraft.util.maths.VertexXY
import org.joda.time.DateTime

import scala.util.Try


private[graphics] class RenderFrame(val gameWorld: Simulator)
    extends GLEventListener {
  val DebugMode = false

  implicit var fbo: FramebufferObject = null
  implicit var renderStack: RenderStack = null
  var camera = new Camera2D
  var textRenderer: TextRenderer = null
  var largeTextRenderer: TextRenderer = null
  val modelCache = new TheModelCache
  val compositeModelBuilderCache = new TheCompositeModelBuilderCache
  var isGL4Supported = false
  lazy val context = new GraphicsContext(renderStack, false, modelCache, compositeModelBuilderCache)

  var cullFaceToggle = false
  val FrametimeSamples = 100
  var frameTimes = scala.collection.mutable.Queue.fill(FrametimeSamples - 1)(new DateTime().getMillis)
  var textField: TextField = null
  var error = false
  @volatile var rendering = true


  override def display(drawable: GLAutoDrawable): Unit = {
    implicit val gl = drawable.getGL
    import gl._

    if (rendering) println("WARNING: Concurrent render calls!")
    rendering = true

    Material.resetDrawCalls()
    Material.resetModelviewUploads()

    if (cullFaceToggle) glEnable(GL_CULL_FACE)
    else glDisable(GL_CULL_FACE)
    cullFaceToggle = !cullFaceToggle

    // draw to texture
    if (isGL4Supported) {
      glBindFramebuffer(GL_FRAMEBUFFER, fbo.fbo)
      glViewport(0, 0, camera.screenWidth * 2, camera.screenHeight * 2)
    } else {
      glViewport(0, 0, camera.screenWidth, camera.screenHeight)
    }

    if (!error) glClearColor(0.02f, 0.02f, 0.02f, 0.0f)
    else glClearColor(0.1f, 0, 0.0f, 0.0f)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    val (worldObjects, textModels) = gameWorld.dequeueFrame()
    val projection = camera.projection

    for (material <- renderStack.materials) {
      material.beforeDraw(projection)

      for (worldObject <- worldObjects) {
        try {
          worldObject.closedModel(gameWorld.timestep, context).draw(material)
        } catch {
          case t: Throwable =>
            println(s"Encountered error while trying to draw $worldObject.")
            t.printStackTrace()
        }
      }


      material.afterDraw()
    }

    // draw from texture to screen
    renderStack.postDraw(camera)

    renderText(drawable, textModels)

    // dispose one-time VBOs
    context.freeTempVBOs(gl)

    // update fps
    val now = new DateTime().getMillis
    frameTimes.enqueue(now)
    val tThen = frameTimes.dequeue()
    val fps = FrametimeSamples * 1000 / (now - tThen)
    textField.setText(
      f"FPS: $fps   " +
      f"TPS: ${gameWorld.measuredFramerate}   " +
      f"Draw calls: ${Material.drawCalls}   " +
      f"Modelview uploads: ${Material.modelviewUploads}   " +
      f"Cached models: ${modelCache.CachedModelCount}   " +
      f"Allocated VBOs: ${VBO.count}   " +
      f"Timestep: ${gameWorld.timestep}   " +
      f"Last cached model: ${modelCache.lastCachedModel}"
    )

    rendering = false
  }

  private def renderText(drawable: GLAutoDrawable, textModels: Iterable[TextModel]): Unit = {
    val gl = drawable.getGL
    val width = drawable.getSurfaceWidth
    val height = drawable.getSurfaceHeight
    gl.getGL2.glBindVertexArray(0)
    gl.getGL2.glUseProgram(0)

    textRenderer.beginRendering(width, height)

    for (text <- textModels if !text.largeFont)
      renderTextModel(textRenderer, text, width, height)

    textRenderer.setColor(1, 1, 1, 0.7f)
    var yPos = height - 15
    val minHeight = textRenderer.getBounds("A").getHeight.toInt
    for (line <- infoText.split("\n")) {
      textRenderer.draw(line, 0, yPos)
      yPos = yPos - scala.math.max(textRenderer.getBounds(line).getHeight.toInt, minHeight)
    }

    textRenderer.endRendering()

    largeTextRenderer.beginRendering(width, height)
    for (text <- textModels if text.largeFont)
      renderTextModel(largeTextRenderer, text, width, height)
    largeTextRenderer.endRendering()
  }

  def renderTextModel(renderer: TextRenderer, textModel: TextModel, width: Int, height: Int): Unit = {
    val TextModel(text, xPos, yPos, color, absolutePos, centered, _) = textModel
    renderer.setColor(color.r, color.g, color.b, color.a)
    val bounds = renderer.getBounds(text)
    val worldPos = VertexXY(xPos, yPos)
    val center =
      if (centered) VertexXY(-bounds.getWidth.toFloat / 2, bounds.getHeight.toFloat / 2)
      else VertexXY(0, 0)
    val cameraPos =
      if (absolutePos) VertexXY(xPos * width / 2, yPos * height / 2)
      else (1 / camera.zoomFactor) * (worldPos - VertexXY(camera.x, camera.y))
    val position = cameraPos + center + VertexXY(width / 2, height / 2)
    renderer.draw(text, position.x.toInt, position.y.toInt)
  }


  private def infoText: String =
    s"""Game speed target: ${gameWorld.framerateTarget}
       |
       |Move camera: WASD, arrow keys
       |Zoom in/out: QE, Page Up/Down
       |${if (gameWorld.isPaused) "Resume game" else "Pause game"}: Spacebar
       |Increase/decrease game speed: F/R
       |Slow mode: P
       |""".stripMargin + gameWorld.additionalInfoText


  def dispose(arg0: GLAutoDrawable): Unit = context.dispose(arg0.getGL)


  def init(drawable: GLAutoDrawable): Unit = {
    printGLInfo(drawable, drawable.getGL)
    isGL4Supported = performVersionCheck(drawable.getGL)

    getEitherGL(drawable) match {
      case Left(gl2) =>
        renderStack = new JVMGL2RenderStack()(gl2)
      case Right(gl4) =>
        // vsync to prevent screen tearing.
        // seems to work with Ubuntu + i3, but might not be portable
        gl4.setSwapInterval(1)
        fbo = new FramebufferObject()(gl4)
        renderStack = new JVMRenderStack()(gl4, fbo)
    }

    textRenderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 14))
    largeTextRenderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 90))
    camera.position = (gameWorld.initialCameraPos.x.toInt, gameWorld.initialCameraPos.y.toInt)
    gameWorld.run()
  }

  def printGLInfo(drawable: GLAutoDrawable, gl: GL): Unit = {
    import gl._
    println("Chosen GLCapabilities: " + drawable.getChosenGLCapabilities)
    println("INIT GL IS: " + gl.getClass.getName)
    println("GL_VENDOR: " + glGetString(GL_VENDOR))
    println("GL_RENDERER: " + glGetString(GL_RENDERER))
    println("GL_VERSION: " + glGetString(GL_VERSION))
  }

  def performVersionCheck(gl: GL): Boolean = {
    val versionString = gl.glGetString(GL_VERSION)
    val isGL2OrGL3 = versionString.startsWith("2") || versionString.startsWith("3")
    val gl4Supported = !gameWorld.forceGL2 && !isGL2OrGL3 && Try { gl.getGL4 }.isSuccess
    val gl2Supported = Try { gl.getGL2 }.isSuccess
    if (!gl2Supported && !gl4Supported) {
      println("Failed to obtain OpenGL graphics device :(\n" +
        "CodeCraft requires OpenGL version 2.0 or higher, which your hardware does not seem to support.")
    }
    gl4Supported
  }

  def reshape(drawable: GLAutoDrawable, x: Int, y: Int, width: Int, height: Int): Unit = {
    camera.screenDims = (width, height)

    for (gl4 <- getGL4(drawable)) fbo.resize(width, height)(gl4)

    textRenderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 14))
    largeTextRenderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 90))
  }

  def getGL4(drawable: GLAutoDrawable): Option[GL4] =
    if (isGL4Supported) Some(
      if (DebugMode) new DebugGL4(drawable.getGL.getGL4)
      else drawable.getGL.getGL4
    ) else None

  def getEitherGL(drawable: GLAutoDrawable): Either[GL2, GL4] = {
    if (isGL4Supported) Right(drawable.getGL.getGL4)
    else Left(drawable.getGL.getGL2)
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy