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

cwinter.codecraft.graphics.materials.JSMaterial.scala Maven / Gradle / Ivy

The newest version!
package cwinter.codecraft.graphics.materials

import cwinter.codecraft.graphics.model.{JSVBO, VBO}
import cwinter.codecraft.util.maths.matrices.Matrix4x4
import cwinter.codecraft.util.maths.{Vertex, VertexManifest}
import org.scalajs.dom.raw.{WebGLProgram, WebGLRenderingContext => GL, WebGLShader}

import scala.language.implicitConversions
import scala.scalajs.js
import scala.scalajs.js.typedarray.Float32Array


/**
 * Vertex shader: code run on GPU to transform vertex positions
 * Fragment shader: code run on GPU to determine pixel colours
 * Program: can be used to store and then reference a vertex + fragment shader on the GPU
 * Vertex Buffer Object: unstructured vertex data
 * (Vertex) Attribute: input parameter to a shader
 * Vertex Attribute Object: maps data from robowars.graphics.model.VBO to one or more attributes
 */
private[graphics] class JSMaterial[TPosition <: Vertex, TColor <: Vertex, TParams](
  val gl: GL,
  vsSource: String,
  fsSource: String,
  attributeNamePos: String,
  attributeNameCol: Option[String],
  enableCaps: Int*
)(implicit
  val posVM: VertexManifest[TPosition],
  val colVM: VertexManifest[TColor]
) extends Material[TPosition, TColor, TParams]{

  val nCompPos = posVM.nComponents
  val nCompCol = colVM.nComponents
  val nComponents = nCompPos + nCompCol


  /*#################
   # INITIALISATION #
   #################*/

  // compile shaders and attach to program
  protected val programID = gl.createProgram()
  protected val vertexShaderID = compileShader(vsSource, GL.VERTEX_SHADER, programID)
  protected val fragmentShaderID = compileShader(fsSource, GL.FRAGMENT_SHADER, programID)
  gl.linkProgram(programID)

  val uniformProjection = gl.getUniformLocation(programID, "projection")
  val uniformModelview = gl.getUniformLocation(programID, "modelview")

  val attributePos = gl.getAttribLocation(programID, attributeNamePos)
  val attributeCol = attributeNameCol.map(gl.getAttribLocation(programID, _))

  private var lastModelview: Matrix4x4 = null


  implicit def arrayFloatToFloat32Array(seq: Array[Float]): Float32Array = {
    import js.JSConverters._
    new Float32Array(seq.toJSArray)
  }

  implicit def VBOToJSVBO(vbo: VBO): JSVBO = {
    assert(vbo.isInstanceOf[JSVBO], s"Expected vbo of type JSVBO. Actual: ${vbo.getClass.getName}")
    vbo.asInstanceOf[JSVBO]
  }

  /*###################
   # PUBLIC INTERFACE #
   ###################*/

  def beforeDraw(projection: Matrix4x4): Unit = {
    lastModelview = null
    gl.useProgram(programID)

    gl.uniformMatrix4fv(
      uniformProjection,
      transpose=false,
      projection.data)

    enableCaps.foreach(gl.enable)
  }

  def draw(vbo: VBO, modelview: Matrix4x4): Unit = {
    Material._drawCalls += 1
    if (vbo.size == 0) return

    // upload modelview
    if (lastModelview ne modelview) {
      gl.uniformMatrix4fv(uniformModelview, transpose = false, modelview.data)
      lastModelview = modelview
      Material._modelviewUploads += 1
    }

    // bind vbo and enable attributes
    gl.bindBuffer(GL.ARRAY_BUFFER, vbo.id)

    // bind shader attributes (input parameters)
    gl.enableVertexAttribArray(attributePos)
    attributeCol.foreach(gl.enableVertexAttribArray)
    gl.vertexAttribPointer(attributePos, nCompPos, GL.FLOAT, normalized=false, 4 * nComponents, 0)
    attributeCol.foreach(gl.vertexAttribPointer(_, nCompCol, GL.FLOAT, normalized=false, 4 * nComponents, 4 * nCompPos))

    // actual drawing call
    gl.drawArrays(GL.TRIANGLES, 0, vbo.size)
  }

  def afterDraw(): Unit = {
    enableCaps.foreach(gl.disable)

    // disable attributes
    gl.disableVertexAttribArray(attributePos)
    attributeCol.foreach(gl.disableVertexAttribArray)
  }


  /**
   * Allocates a VBO handle, loads vertex data into GPU and defines attribute pointers.
   * @param data The data for the VBO.
   * @return Returns a `robowars.graphics.model.VBO` class which give the handle and number of data of the vbo.
   */
  def createVBO(data: Array[Float], dynamic: Boolean): VBO = {
    // create vbo handle
    val vboHandle = gl.createBuffer()

    // store data to GPU
    gl.bindBuffer(GL.ARRAY_BUFFER, vboHandle)
    gl.bufferData(GL.ARRAY_BUFFER, data, if (dynamic) GL.DYNAMIC_DRAW else GL.STATIC_DRAW)

    VBO._count += 1
    JSVBO(vboHandle, data.length / nComponents)
  }


  /*##################
   # PRIVATE METHODS #
   ##################*/


  /**
   * Compile a shader and attach to a program.
   * @param shaderSource The source code for the shader.
   * @param shaderType The type of shader (`GL2ES2.GL.VERTEX_SHADER` or `GL2ES2.GL.FRAGMENT_SHADER`)
   * @param programID The handle to the program.
   * @return
   */
  protected def compileShader(
    shaderSource: String,
    shaderType: Int,
    programID: WebGLProgram
  ): WebGLShader = {

    // Create GPU shader handles
    // OpenGL returns an index id to be stored for future reference.
    val shaderHandle = gl.createShader(shaderType)

    // bind shader to program
    gl.attachShader(programID, shaderHandle)


    // Load shader source code and compile into a program
    gl.shaderSource(shaderHandle, shaderSource)
    gl.compileShader(shaderHandle)

    val compileStatus = gl.getShaderParameter(shaderHandle, GL.COMPILE_STATUS)
    if (!compileStatus.asInstanceOf[Boolean]) {
      throw new Exception(gl.getShaderInfoLog(shaderHandle))
    }

    shaderHandle
  }


  override def dispose(): Unit = {
    gl.deleteProgram(programID)
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy