├── project ├── build.properties └── plugins.sbt ├── example ├── src │ └── main │ │ ├── resources │ │ └── mesh │ │ │ ├── texture │ │ │ ├── DS1_tex_01.psd │ │ │ ├── foil_n.png │ │ │ ├── foil_gold_ramp.png │ │ │ └── foil_silver_ramp.png │ │ │ └── deep_space_1_11.mtl │ │ └── scala │ │ ├── CartonShader.scala │ │ └── Main.scala └── third_party │ └── third_party_licenses.md ├── .gitattributes ├── .gitignore ├── shader ├── src │ ├── test │ │ └── scala │ │ │ └── com │ │ │ └── jsuereth │ │ │ └── gl │ │ │ └── shaders │ │ │ ├── helpers.scala │ │ │ ├── TestTextureUniformInShader.scala │ │ │ ├── TestMathTranslations.scala │ │ │ ├── TestStructUniformShader.scala │ │ │ └── TestShader.scala │ └── main │ │ └── scala │ │ └── com │ │ └── jsuereth │ │ └── gl │ │ └── shaders │ │ ├── Uniform.scala │ │ ├── codegen │ │ ├── ast.scala │ │ ├── transform.scala │ │ └── convertors.scala │ │ ├── DslShaderProgram.scala │ │ └── BasicShaderProgram.scala └── third_party │ └── third_party_licenses.md ├── .travis.yml ├── scene ├── third_party │ └── third_party_licenses.md └── src │ ├── test │ └── scala │ │ └── com │ │ └── jsuereth │ │ └── gl │ │ └── mesh │ │ └── parser │ │ ├── TestMtlParser.scala │ │ └── TestObjParser.scala │ └── main │ └── scala │ └── com │ └── jsuereth │ └── gl │ ├── scene │ ├── SceneObject.scala │ ├── Scene.scala │ └── Camera.scala │ └── mesh │ ├── Material.scala │ ├── parser │ ├── MtlParser.scala │ └── ObjParser.scala │ └── Mesh.scala ├── math ├── third_party │ └── third_party_licenses.md └── src │ ├── main │ └── scala │ │ └── com │ │ └── jsuereth │ │ └── gl │ │ └── math │ │ ├── Comparable.scala │ │ ├── ops.scala │ │ ├── UInt.scala │ │ ├── Vec2.scala │ │ ├── SquareRootable.scala │ │ ├── Trigonometry.scala │ │ ├── Vec4.scala │ │ ├── Matrix3.scala │ │ ├── Vec3.scala │ │ ├── Quaternion.scala │ │ └── Matrix4.scala │ └── test │ └── scala │ └── com │ └── jsuereth │ └── gl │ └── math │ └── math │ └── TestVec3.scala ├── CONTRIBUTING.md ├── io ├── src │ ├── test │ │ └── scala │ │ │ └── com │ │ │ └── jsuereth │ │ │ └── gl │ │ │ └── io │ │ │ ├── TestBufferLoadable.scala │ │ │ ├── TestShaderUniformLoadable.scala │ │ │ └── TestVaoAttribute.scala │ └── main │ │ └── scala │ │ └── com │ │ └── jsuereth │ │ └── gl │ │ ├── io │ │ ├── GlBuffer.scala │ │ ├── StackUtil.scala │ │ ├── FrameBufferObject.scala │ │ ├── ActiveTextures.scala │ │ ├── VertexArrayObject.scala │ │ ├── BufferLoadable.scala │ │ ├── VaoAttribute.scala │ │ └── ShaderUniformLoadable.scala │ │ └── texture │ │ ├── TextureParameter.scala │ │ └── Texture.scala └── third_party │ └── third_party_licenses.md ├── README.md ├── CODE_OF_CONDUCT.md ├── types.md └── LICENSE.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.7 2 | -------------------------------------------------------------------------------- /example/src/main/resources/mesh/texture/DS1_tex_01.psd: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.scala text eolf=lf 2 | *.sh text eol=lf 3 | * text=auto 4 | -------------------------------------------------------------------------------- /example/src/main/resources/mesh/texture/foil_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/shady-side/HEAD/example/src/main/resources/mesh/texture/foil_n.png -------------------------------------------------------------------------------- /example/src/main/resources/mesh/texture/foil_gold_ramp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/shady-side/HEAD/example/src/main/resources/mesh/texture/foil_gold_ramp.png -------------------------------------------------------------------------------- /example/src/main/resources/mesh/texture/foil_silver_ramp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuereth/shady-side/HEAD/example/src/main/resources/mesh/texture/foil_silver_ramp.png -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0") 2 | addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.3.3") 3 | addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0") 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .metals 8 | .project 9 | .bloop 10 | 11 | # idea 12 | out 13 | *.ipr 14 | *.iws 15 | *.iml 16 | .idea 17 | 18 | # sbt 19 | target/ 20 | 21 | # other 22 | eclipse 23 | run 24 | -------------------------------------------------------------------------------- /shader/src/test/scala/com/jsuereth/gl/shaders/helpers.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl 2 | package test 3 | 4 | import org.junit.Assert 5 | 6 | def cleanLineEndings(in: String): String = 7 | in.replaceAll("\r\n", "\n") 8 | 9 | def assertCleanEquals(expected: String, actual: String): Unit = 10 | Assert.assertEquals(cleanLineEndings(expected), cleanLineEndings(actual)) -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | script: 4 | - sbt compile 5 | - sbt test 6 | 7 | before_cache: 8 | - find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete 9 | - find $HOME/.ivy2 -name "ivydata-*.properties" -delete 10 | - find $HOME/.sbt -name "*.lock" -delete 11 | 12 | cache: 13 | directories: 14 | - $HOME/.cache/coursier/v1 15 | - $HOME/.ivy2/cache 16 | - $HOME/.sbt/boot 17 | -------------------------------------------------------------------------------- /scene/third_party/third_party_licenses.md: -------------------------------------------------------------------------------- 1 | # third_party_licenses 2 | 3 | Category | License | Dependency | Notes 4 | --- | --- | --- | --- 5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # io_0.16 # 0.1.0 | 6 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # math_0.16 # 0.1.0 | 7 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 | 8 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl # 3.2.2 | Lightweight Java Game Library 9 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-opengl # 3.2.2 | Lightweight Java Game Library 10 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 | 11 | 12 | -------------------------------------------------------------------------------- /math/third_party/third_party_licenses.md: -------------------------------------------------------------------------------- 1 | # third_party_licenses 2 | 3 | Category | License | Dependency | Notes 4 | --- | --- | --- | --- 5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 | 6 | BSD | [BSD](https://github.com/sbt/test-interface/blob/master/LICENSE) | org.scala-sbt # test-interface # 1.0 | 7 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 | 8 | BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 | 9 | BSD | [Two-clause BSD-style license](http://github.com/sbt/junit-interface/blob/master/LICENSE.txt) | com.novocode # junit-interface # 0.11 | Used for testing 10 | Common Public License | [Common Public License Version 1.0](http://www.opensource.org/licenses/cpl1.0.txt) | junit # junit # 4.11 | Used for testing 11 | 12 | -------------------------------------------------------------------------------- /shader/src/main/scala/com/jsuereth/gl/shaders/Uniform.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package shaders 19 | 20 | import io.ShaderLoadingEnvironment 21 | 22 | /** A marker trait for how we pass data in/out of an open-gl shader. */ 23 | trait Uniform[T] { 24 | /** The name we use for the uniform. */ 25 | def name: String 26 | /** Writes a value into a shader program. */ 27 | def :=(x: T)(using ShaderLoadingEnvironment): Unit 28 | } -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/Comparable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.math 18 | 19 | import scala.math.Ordering 20 | 21 | // This file defines helper methods to replace the use of java.lang.Math. 22 | 23 | /** Returns the maximum value of two options. */ 24 | def max[T: Ordering](lhs: T, rhs: T): T = summon[Ordering[T]].max(lhs,rhs) 25 | /** Returns the minimum value of two options. */ 26 | def min[T: Ordering](lhs: T, rhs: T): T = summon[Ordering[T]].min(lhs,rhs) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows 28 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /io/src/test/scala/com/jsuereth/gl/io/TestBufferLoadable.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl 2 | package io 3 | package testbufferlodable 4 | 5 | import math._ 6 | 7 | import org.junit.Test 8 | import org.junit.Assert._ 9 | import java.nio.ByteBuffer 10 | 11 | case class ExamplePod(x: Vec3[Float], y: Vec2[Int], z: Vec2[Boolean]) derives BufferLoadable 12 | class TestBufferLoadable { 13 | 14 | @Test def derivedLoadableWorks(): Unit = { 15 | val buf = ByteBuffer.allocate(sizeOf[ExamplePod]) 16 | buf.load(ExamplePod(Vec3(1f,2f,3f), Vec2(4,5), Vec2(true, false))) 17 | buf.flip 18 | assertEquals(1f, buf.getFloat, 0.000001f) 19 | assertEquals(2f, buf.getFloat, 0.000001f) 20 | assertEquals(3f, buf.getFloat, 0.000001f) 21 | assertEquals(4, buf.getInt) 22 | assertEquals(5, buf.getInt) 23 | assertEquals(1.toByte, buf.get) 24 | assertEquals(0.toByte, buf.get) 25 | } 26 | @Test def primitiveWorks(): Unit = { 27 | val buf = ByteBuffer.allocate(12) 28 | buf.load(1f) 29 | buf.load(2) 30 | buf.load(false) 31 | buf.load(2.toByte) 32 | buf.flip 33 | assertEquals(1f, buf.getFloat, 0.00001f) 34 | assertEquals(2, buf.getInt) 35 | assertEquals(0.toByte, buf.get) 36 | assertEquals(2.toByte, buf.get) 37 | } 38 | } -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/ops.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.math 18 | 19 | import scala.math.Numeric 20 | import scala.math.Fractional 21 | 22 | extension ops on [T](x: T)(using n: Numeric[T]) { 23 | def +(y: T): T = n.plus(x,y) 24 | def -(y: T): T = n.minus(x,y) 25 | def *(y: T): T = n.times(x,y) 26 | // Open bug about this not working. 27 | def unary_- = n.negate(x) 28 | } 29 | 30 | def [T](x: T) unary_-(using n: Numeric[T]): T = n.negate(x) 31 | 32 | extension fractionalOps on [T](x: T)(using f: Fractional[T]) { 33 | def /(y: T) = f.div(x, y) 34 | } 35 | 36 | def zero[T: Numeric]: T = summon[Numeric[T]].zero 37 | def one[T: Numeric]: T = summon[Numeric[T]].one 38 | def two[T: Numeric]: T = one + one 39 | -------------------------------------------------------------------------------- /example/third_party/third_party_licenses.md: -------------------------------------------------------------------------------- 1 | # third_party_licenses 2 | 3 | Category | License | Dependency | Notes 4 | --- | --- | --- | --- 5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # io_0.16 # 0.1.0 | 6 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # math_0.16 # 0.1.0 | 7 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # scene_0.16 # 0.1.0 | 8 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # shader_0.16 # 0.1.0 | 9 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 | 10 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl # 3.2.2 | Lightweight Java Game Library 11 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-glfw # 3.2.2 | Lightweight Java Game Library 12 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-opengl # 3.2.2 | Lightweight Java Game Library 13 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 | 14 | 15 | -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/io/GlBuffer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.io 18 | 19 | import org.lwjgl.system.MemoryStack 20 | import org.lwjgl.opengl.GL30.{GL_FRAMEBUFFER,glBindFramebuffer,glGenFramebuffers} 21 | 22 | 23 | /** A helper to represent a buffer in OpenGL. We can load things into this buffer. */ 24 | trait GLBuffer extends AutoCloseable { 25 | /* The ID of the buffer object. This makes no sense to use unless you know the type of buffer (FBO/VBO). */ 26 | def id: Int 27 | // all instances should have an inline "with" 28 | inline def withBound[A](f: => A): A = { 29 | bind() 30 | try f 31 | finally unbind() 32 | } 33 | /** Binds the buffer to be used. */ 34 | def bind(): Unit 35 | /** Unbinds the buffer. */ 36 | def unbind(): Unit 37 | } -------------------------------------------------------------------------------- /io/third_party/third_party_licenses.md: -------------------------------------------------------------------------------- 1 | # third_party_licenses 2 | 3 | Category | License | Dependency | Notes 4 | --- | --- | --- | --- 5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # math_0.16 # 0.1.0 | 6 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 | 7 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl # 3.2.2 | Lightweight Java Game Library 8 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-opengl # 3.2.2 | Lightweight Java Game Library 9 | BSD | [BSD](https://github.com/sbt/test-interface/blob/master/LICENSE) | org.scala-sbt # test-interface # 1.0 | 10 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 | 11 | BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 | 12 | BSD | [Two-clause BSD-style license](http://github.com/sbt/junit-interface/blob/master/LICENSE.txt) | com.novocode # junit-interface # 0.11 | Used for testing 13 | Common Public License | [Common Public License Version 1.0](http://www.opensource.org/licenses/cpl1.0.txt) | junit # junit # 4.11 | Used for testing 14 | 15 | -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/UInt.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package math 19 | 20 | // TODO - move this somewhere it'll work. 21 | opaque type UInt = Int 22 | 23 | def (x: UInt) toLong: Long = x & 0xffffffffL 24 | def (x: UInt) + (y: UInt): UInt = x + y 25 | def (x: UInt) - (y: UInt): UInt = x - y 26 | def (x: UInt) * (y: UInt): UInt = x * y 27 | def (x: UInt) / (y: UInt): UInt = (x.toLong / y.toLong).toInt 28 | def (x: UInt) < (y: UInt): Boolean = x.toLong < y.toLong 29 | def (x: UInt) <= (y: UInt): Boolean = x.toLong <= y.toLong 30 | def (x: UInt) > (y: UInt): Boolean = x.toLong > y.toLong 31 | def (x: UInt) >= (y: UInt): Boolean = x.toLong >= y.toLong 32 | def (x: UInt) == (y: UInt): Boolean = x == y 33 | def (x: UInt) != (y: UInt): Boolean = x != y 34 | 35 | object UInt { 36 | def apply(x: Int): UInt = x 37 | def apply(x: Long): UInt = x.toInt 38 | } -------------------------------------------------------------------------------- /io/src/test/scala/com/jsuereth/gl/io/TestShaderUniformLoadable.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl 2 | package io 3 | 4 | import org.junit.Test 5 | import org.junit.Assert._ 6 | import org.lwjgl.opengl.GL11.{GL_FLOAT, GL_INT, GL_SHORT, GL_BYTE,GL_DOUBLE} 7 | 8 | case class UniformExamplePod(one: math.Vec2[Int], two: math.Vec3[Float]) 9 | case class UniformExamplePod2(one: math.Vec2[Int], two: math.Vec3[Float], three: math.Vec2[Float], four: math.Vec2[Float]) 10 | case class UniformExamplePod3(one: math.Vec2[Int], two: UniformExamplePod) 11 | 12 | class TestUniformSize { 13 | import ShaderUniformLoadable.uniformSize 14 | 15 | @Test def sizeOfPrimitive(): Unit = { 16 | assertEquals(1, uniformSize[math.Vec2[Int]]) 17 | assertEquals(1, uniformSize[math.Vec3[Float]]) 18 | assertEquals(1, uniformSize[math.Matrix4x4[Float]]) 19 | } 20 | @Test def sizeOfStruct(): Unit = { 21 | assertEquals(2, uniformSize[UniformExamplePod]) 22 | // Recursive size test. 23 | assertEquals(3, uniformSize[UniformExamplePod3]) 24 | assertEquals(4, uniformSize[UniformExamplePod2]) 25 | } 26 | 27 | // The test here is if we compile. Calling OpenGL at this point will cause an error, since we have no gl context. 28 | @Test def deriveUnfiromCompiles(): Unit = { 29 | given testPod as ShaderUniformLoadable[UniformExamplePod] = ShaderUniformLoadable.derived[UniformExamplePod] 30 | // Test nested derivation. 31 | given testPod2 as ShaderUniformLoadable[UniformExamplePod3] = ShaderUniformLoadable.derived[UniformExamplePod3] 32 | } 33 | } -------------------------------------------------------------------------------- /shader/third_party/third_party_licenses.md: -------------------------------------------------------------------------------- 1 | # third_party_licenses 2 | 3 | Category | License | Dependency | Notes 4 | --- | --- | --- | --- 5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # io_0.16 # 0.1.0 | 6 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # math_0.16 # 0.1.0 | 7 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 | 8 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl # 3.2.2 | Lightweight Java Game Library 9 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-opengl # 3.2.2 | Lightweight Java Game Library 10 | BSD | [BSD](https://github.com/sbt/test-interface/blob/master/LICENSE) | org.scala-sbt # test-interface # 1.0 | 11 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 | 12 | BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 | 13 | BSD | [Two-clause BSD-style license](http://github.com/sbt/junit-interface/blob/master/LICENSE.txt) | com.novocode # junit-interface # 0.11 | Used for testing 14 | Common Public License | [Common Public License Version 1.0](http://www.opensource.org/licenses/cpl1.0.txt) | junit # junit # 4.11 | Used for testing 15 | 16 | -------------------------------------------------------------------------------- /shader/src/test/scala/com/jsuereth/gl/shaders/TestTextureUniformInShader.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl 2 | package shaders 3 | package testtexture 4 | 5 | import org.junit.Test 6 | import org.junit.Assert._ 7 | 8 | import com.jsuereth.gl.shaders._ 9 | import com.jsuereth.gl.math._ 10 | import com.jsuereth.gl.test.assertCleanEquals 11 | import com.jsuereth.gl.texture.Texture2D 12 | 13 | /** This is our target syntax. */ 14 | // Attempt at cel-shading 15 | import com.jsuereth.gl.math.{given _, _} 16 | 17 | object TextureShader extends DslShaderProgram { 18 | 19 | // User for pixel shader / lighting model 20 | val diffuseTexture = Uniform[Texture2D]() 21 | 22 | val (vertexShaderCode, fragmentShaderCode) = defineShaders { 23 | val inPosition = Input[Vec3[Float]](location=0) 24 | glPosition(Vec4(inPosition, 1)) 25 | // Fragment shader 26 | fragmentShader { 27 | Output("color", 0, diffuseTexture().texture(Vec2(0f,0f))) 28 | } 29 | } 30 | } 31 | 32 | class TestTextureShaders { 33 | @Test def createsCorrectVertexShader(): Unit = { 34 | assertCleanEquals( 35 | """#version 300 es 36 | 37 | precision highp float; 38 | precision highp int; 39 | 40 | layout (location = 0) in vec3 inPosition; 41 | void main() { 42 | gl_Position = vec4(inPosition,1.0); 43 | }""", TextureShader.vertexShaderCode) 44 | } 45 | @Test def createsCorrectFragmentShader(): Unit = { 46 | assertCleanEquals( 47 | """#version 300 es 48 | 49 | precision highp float; 50 | precision highp int; 51 | 52 | layout (location = 0) out vec4 color; 53 | uniform sampler2D diffuseTexture; 54 | void main() { 55 | color = texture(diffuseTexture,vec2(0.0,0.0)); 56 | }""", TextureShader.fragmentShaderCode) 57 | } 58 | } -------------------------------------------------------------------------------- /shader/src/test/scala/com/jsuereth/gl/shaders/TestMathTranslations.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl 2 | package shaders 3 | package testmathtranslation 4 | 5 | import org.junit.Test 6 | import org.junit.Assert._ 7 | import com.jsuereth.gl.math._ 8 | import com.jsuereth.gl.test.assertCleanEquals 9 | 10 | /** This is our target syntax. */ 11 | // Attempt at cel-shading 12 | import com.jsuereth.gl.math.{given _, _} 13 | object ExampleShader extends DslShaderProgram { 14 | // Used for vertex shader 15 | val lightPosition = Uniform[Vec3[Float]]() 16 | val eyePosition = Uniform[Vec3[Float]]() 17 | 18 | val (vertexShaderCode, fragmentShaderCode) = defineShaders { 19 | val inPosition = Input[Vec3[Float]](location=0) 20 | glPosition(Vec4(inPosition, 1)) 21 | // Fragment shader 22 | fragmentShader { 23 | val L = pow(1f, max(min(0f, lightPosition().x), lightPosition().y)) 24 | Output("color", 0, Vec4(L, L, L, 1f)) 25 | } 26 | } 27 | } 28 | 29 | class Test1 { 30 | @Test def extractVertexShaderProgramString(): Unit = { 31 | assertCleanEquals( 32 | """#version 300 es 33 | 34 | precision highp float; 35 | precision highp int; 36 | 37 | layout (location = 0) in vec3 inPosition; 38 | void main() { 39 | gl_Position = vec4(inPosition,1.0); 40 | }""", ExampleShader.vertexShaderCode) 41 | } 42 | @Test def extractFragmentShaderProgramString(): Unit = { 43 | assertCleanEquals( 44 | """#version 300 es 45 | 46 | precision highp float; 47 | precision highp int; 48 | 49 | uniform vec3 lightPosition; 50 | layout (location = 0) out vec4 color; 51 | void main() { 52 | float L = pow(1.0,max(min(0.0,(lightPosition).x),(lightPosition).y)); 53 | color = vec4(L,L,L,1.0); 54 | }""", ExampleShader.fragmentShaderCode) 55 | } 56 | } -------------------------------------------------------------------------------- /scene/src/test/scala/com/jsuereth/gl/mesh/parser/TestMtlParser.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl 2 | package mesh 3 | package parser 4 | 5 | import org.junit.Test 6 | import org.junit.Assert._ 7 | import math._ 8 | 9 | class TestMtlParser { 10 | def stream(in: String): java.io.InputStream = 11 | java.io.ByteArrayInputStream(in.getBytes) 12 | 13 | @Test def parseSimpleMaterial(): Unit = { 14 | val materials = MtlFileParser.parse(stream("""| 15 | |newmtl default 16 | |Ns 96.0 17 | |Ka 0.000000 0.000000 1.000000 18 | |Kd 0.640000 0.640000 0.640000 19 | |Ks 0.500000 0.500000 0.500000 20 | |Ni 1.000000 21 | |d 1.000000 22 | |illum 2 23 | |map_Kd blue-sky.jpg 24 | |""".stripMargin('|'))) 25 | 26 | assertEquals(1, materials.size) 27 | val mtl = materials("default") 28 | assertEquals("Parse ambient color", Vec3(0f,0f,1f), mtl.base.ka) 29 | assertEquals("Parse diffuse color", Vec3(0.64f,0.64f,0.64f), mtl.base.kd) 30 | assertEquals("Parse specular color", Vec3(0.5f,0.5f,0.5f), mtl.base.ks) 31 | assertEquals("Parse specular component", 96.0f, mtl.base.ns, 0.0000001f) 32 | assertEquals("Parse diffuse texture file", Some("blue-sky.jpg"), mtl.textures.kd.map(_.filename)) 33 | assertEquals("Not Parse specular texture file", None, mtl.textures.ks.map(_.filename)) 34 | assertEquals("Not Parse ambient texture file", None, mtl.textures.ka.map(_.filename)) 35 | assertEquals("Not Parse specular component texture file", None, mtl.textures.ns.map(_.filename)) 36 | assertEquals("Not Parse d texture file", None, mtl.textures.d.map(_.filename)) 37 | assertEquals("Not Parse bump map texture file", None, mtl.textures.bump.map(_.filename)) 38 | } 39 | } -------------------------------------------------------------------------------- /shader/src/test/scala/com/jsuereth/gl/shaders/TestStructUniformShader.scala: -------------------------------------------------------------------------------- 1 | import org.junit.Test 2 | import org.junit.Assert._ 3 | 4 | import com.jsuereth.gl.shaders._ 5 | import com.jsuereth.gl.math._ 6 | import com.jsuereth.gl.io._ 7 | import com.jsuereth.gl.test.assertCleanEquals 8 | 9 | /** This is our target syntax. */ 10 | import com.jsuereth.gl.math.{given _, _} 11 | 12 | case class Material(kd: Float, ks: Float, color: Vec3[Float]) derives ShaderUniformLoadable 13 | object ExampleStructShader extends DslShaderProgram { 14 | val material = Uniform[Material]() 15 | val (vertexShaderCode, fragmentShaderCode) = defineShaders { 16 | val inPosition = Input[Vec3[Float]](location=0) 17 | glPosition(Vec4(inPosition, 1f)) 18 | fragmentShader { 19 | Output("color", 0, Vec4(material().color, 1f)) 20 | } 21 | } 22 | } 23 | 24 | class TestStructs { 25 | @Test def extractVertexShader(): Unit = { 26 | assertCleanEquals( 27 | """#version 300 es 28 | 29 | precision highp float; 30 | precision highp int; 31 | 32 | layout (location = 0) in vec3 inPosition; 33 | void main() { 34 | gl_Position = vec4(inPosition,1.0); 35 | }""", ExampleStructShader.vertexShaderCode) 36 | } 37 | @Test def extractPixelShader(): Unit = { 38 | assertCleanEquals( 39 | """#version 300 es 40 | 41 | precision highp float; 42 | precision highp int; 43 | 44 | struct Material { 45 | float kd; 46 | float ks; 47 | vec3 color; 48 | }; 49 | layout (location = 0) out vec4 color; 50 | uniform Material material; 51 | void main() { 52 | color = vec4((material).color,1.0); 53 | }""", ExampleStructShader.fragmentShaderCode) 54 | // We should find the first value of the structure here, 55 | // since we do offset-based loading. 56 | assertEquals("material.kd", ExampleStructShader.material.name) 57 | } 58 | } -------------------------------------------------------------------------------- /scene/src/main/scala/com/jsuereth/gl/scene/SceneObject.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package scene 19 | 20 | import mesh.Mesh3d 21 | import math.{given _, _} 22 | /** 23 | * Represents an object in the scene. 24 | * TODO - Define how to interact w/ objects, and how we render them. 25 | * 26 | * TODO - define as enum? 27 | */ 28 | trait SceneObject { 29 | /** The mesh representing this object. */ 30 | def mesh: Mesh3d 31 | /** The current position of hte object. */ 32 | def pos: Vec3[Float] 33 | /** The orientation of the object. We use a quaternion to save space on rotational axes. */ 34 | def orientation: Quaternion[Float] 35 | /** Scale of the mesh. */ 36 | def scale: Vec3[Float] 37 | /** Returns the current model-matrix we can use to render this scene object. */ 38 | def modelMatrix: Matrix4[Float] = 39 | Matrix4.translate[Float](pos.x, pos.y, pos.z) * orientation.toMatrix * Matrix4.scale(scale.x, scale.y, scale.z) 40 | } 41 | /** 42 | * A static, unomving object within the scene. 43 | */ 44 | case class StaticSceneObject(mesh: Mesh3d, pos: Vec3[Float], orientation: Quaternion[Float] = Quaternion.identity, scale: Vec3[Float]) extends SceneObject -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/Vec2.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package math 19 | 20 | import scala.math.Numeric 21 | import scala.reflect.ClassTag 22 | 23 | class Vec2[T : ClassTag](private[this] val values: Array[T]) { 24 | def x: T = values(0) 25 | def y: T = values(1) 26 | def rg: Vec2[T] = this 27 | def xy: Vec2[T] = this 28 | 29 | def +(other: Vec2[T])(using Numeric[T]): Vec2[T] = Vec2(x+other.x, y+other.y) 30 | def -(other: Vec2[T])(using Numeric[T]): Vec2[T] = Vec2(x-other.x, y-other.y) 31 | // behaves as GLSL would, just multiplies pairs... 32 | def *(other: Vec2[T])(using Numeric[T]): Vec2[T] = Vec2(x*other.x, y*other.y) 33 | 34 | /** The dot product of this vector and another. */ 35 | def dot(other: Vec2[T])(using Numeric[T]): T = x*other.x + y*other.y 36 | 37 | /** the square of the length of this vector. */ 38 | def lengthSquared(using Numeric[T]): T = this dot this 39 | /** Returns the length of this vector. Requires a SQRT function. */ 40 | def length(using Rootable[T], Numeric[T]): T = sqrt(lengthSquared) 41 | 42 | /** Normalizes this vector (setting distance to 1). Note: This requires a valid SQRT function. */ 43 | def normalize(using Fractional[T], Rootable[T]): Vec2[T] = 44 | new Vec2[T](Array((x / length), (y / length))) 45 | 46 | override def toString: String = s"($x,$y)" 47 | } 48 | object Vec2 { 49 | def apply[T: ClassTag](x: T, y: T): Vec2[T] = new Vec2(Array(x,y)) 50 | } -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/io/StackUtil.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.io 18 | 19 | import org.lwjgl.system.{ 20 | MemoryStack, 21 | MemoryUtil 22 | } 23 | import java.nio.{ 24 | ByteBuffer, 25 | FloatBuffer, 26 | IntBuffer 27 | } 28 | 29 | /** Ensures there is a memory stack available for an operation, popping the stack when complete. */ 30 | inline def withMemoryStack[A](f: MemoryStack ?=> A): A = { 31 | val stack = org.lwjgl.system.MemoryStack.stackPush() 32 | try f(using stack) 33 | finally stack.pop() 34 | } 35 | /** Allocates a byte buffer. Will allocate on the stack, if small enough, otherwise in off-heap memory. */ 36 | inline def withByteBuffer[A](size: Int)(f: ByteBuffer => A)(using stack: MemoryStack): A = 37 | if (size < 4096) f(stack.calloc(size)) 38 | else { 39 | val buf = MemoryUtil.memAlloc(size) 40 | try f(buf) finally MemoryUtil.memFree(buf) 41 | } 42 | /** Allocates a float buffer. Will allocate on the stack, if small enough, otherwise in off-heap memory. */ 43 | inline def withFloatBuffer[A](size: Int)(f: FloatBuffer => A)(using stack: MemoryStack): A = 44 | if (size < 1024) f(stack.callocFloat(size)) 45 | else { 46 | val buf = MemoryUtil.memAllocFloat(size) 47 | try f(buf) finally MemoryUtil.memFree(buf) 48 | } 49 | /** Allocates an int buffer. Will allocate on the stack, if small enough, otherwise in off-heap memory. */ 50 | inline def withIntBuffer[A](size: Int)(f: IntBuffer => A)(using stack: MemoryStack): A = 51 | if (size < 1024) f(stack.callocInt(size)) 52 | else { 53 | val buf = MemoryUtil.memAllocInt(size) 54 | try f(buf) finally MemoryUtil.memFree(buf) 55 | } -------------------------------------------------------------------------------- /example/src/main/resources/mesh/deep_space_1_11.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'Deep_Space_1_11.blend' 2 | # Provided from https://github.com/nasa/NASA-3D-Resources 3 | # Material Count: 8 4 | 5 | newmtl DS1_tex_01 6 | Ns 96.078431 7 | Ka 1.000000 1.000000 1.000000 8 | Kd 0.640000 0.640000 0.640000 9 | Ks 0.500000 0.500000 0.500000 10 | Ke 0.000000 0.000000 0.000000 11 | Ni 1.000000 12 | d 1.000000 13 | illum 2 14 | map_Kd texture/DS1_tex_01.psd 15 | 16 | newmtl None 17 | Ns 0 18 | Ka 0.000000 0.000000 0.000000 19 | Kd 0.8 0.8 0.8 20 | Ks 0.8 0.8 0.8 21 | d 1 22 | illum 2 23 | 24 | newmtl black_krinkle 25 | Ns 100.000000 26 | Ka 1.000000 1.000000 1.000000 27 | Kd 0.003917 0.003917 0.003917 28 | Ks 0.192187 0.192187 0.192187 29 | Ke 0.000000 0.000000 0.000000 30 | Ni 1.000000 31 | d 1.000000 32 | illum 2 33 | map_Kd foil_n.png 34 | map_Bump texture/foil_n.png 35 | 36 | newmtl foil_gold 37 | Ns 96.078431 38 | Ka 1.000000 1.000000 1.000000 39 | Kd 0.557647 0.412941 0.056471 40 | Ks 0.866667 0.850980 0.658824 41 | Ke 0.000000 0.000000 0.000000 42 | Ni 1.000000 43 | d 1.000000 44 | illum 2 45 | map_Kd foil_n.png 46 | map_Bump -bm 0.450000 texture/foil_n.png 47 | refl texture/foil_gold_ramp.png 48 | 49 | newmtl foil_gold_NONE 50 | Ns 96.078431 51 | Ka 1.000000 1.000000 1.000000 52 | Kd 0.557647 0.412941 0.056471 53 | Ks 0.866667 0.850980 0.658824 54 | Ke 0.000000 0.000000 0.000000 55 | Ni 1.000000 56 | d 1.000000 57 | illum 2 58 | map_Bump -bm 0.450000 texture/foil_n.png 59 | refl texture/foil_gold_ramp.png 60 | 61 | newmtl foil_silver 62 | Ns 96.078431 63 | Ka 1.000000 1.000000 1.000000 64 | Kd 0.578934 0.578934 0.578934 65 | Ks 1.000000 1.000000 1.000000 66 | Ke 0.000000 0.000000 0.000000 67 | Ni 1.000000 68 | d 1.000000 69 | illum 2 70 | map_Kd foil_n.png 71 | map_Bump -bm 0.450000 texture/foil_n.png 72 | refl texture/foil_silver_ramp.png 73 | 74 | newmtl foil_silver_NONE 75 | Ns 96.078431 76 | Ka 1.000000 1.000000 1.000000 77 | Kd 0.578934 0.578934 0.578934 78 | Ks 1.000000 1.000000 1.000000 79 | Ke 0.000000 0.000000 0.000000 80 | Ni 1.000000 81 | d 1.000000 82 | illum 2 83 | map_Bump -bm 0.450000 texture/foil_n.png 84 | refl texture/foil_silver_ramp.png 85 | 86 | newmtl shiny_panel 87 | Ns 96.078431 88 | Ka 1.000000 1.000000 1.000000 89 | Kd 0.640000 0.640000 0.640000 90 | Ks 0.500000 0.500000 0.500000 91 | Ke 0.000000 0.000000 0.000000 92 | Ni 1.000000 93 | d 1.000000 94 | illum 2 95 | map_Kd texture/DS1_tex_01.psd 96 | -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/SquareRootable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.math 18 | 19 | /** Denotes that you can take "roots" for a type T. 20 | * 21 | * Effectively, this means we have the following functions: 22 | * - root(value, n) 23 | * - sqrt(value) // root(value,2) 24 | * - pow(n, value) // n^value 25 | */ 26 | trait Rootable[T] { 27 | /** Computes the arbitrary root of a value. pow(root(value, base), base) == value */ 28 | def root(value: T, base: T): T 29 | /** Computes the square root of a value. */ 30 | def sqrt(value: T): T 31 | /** Exponential multiplication. base^exp. */ 32 | def pow(base: T, exp: T): T 33 | } 34 | 35 | object Rootable 36 | given Rootable[Float] { 37 | // Approximation for arbitrary roots. 38 | def root(value: Float, base: Float): Float = pow(value, 1f / base) 39 | def sqrt(value: Float): Float = Math.sqrt(value.toDouble).toFloat 40 | def pow(base: Float, exp: Float): Float = Math.pow(base.toDouble, exp.toDouble).toFloat 41 | } 42 | given Rootable[Double] { 43 | // Approximation for arbitrary roots. 44 | def root(value: Double, base: Double): Double = Math.pow(Math.E, Math.log(base) / value.toDouble) 45 | def sqrt(value: Double): Double = Math.sqrt(value) 46 | def pow(base: Double, exp: Double): Double = Math.pow(base, exp) 47 | } 48 | 49 | 50 | /** package-level method to compute sqrt on any type that's Rootable. */ 51 | def sqrt[T](value: T)(using r: Rootable[T]): T = r.sqrt(value) 52 | /** package-level method to compute a root on any type that's Rootable. */ 53 | def root[T](value: T, base: T)(using r: Rootable[T]): T = r.root(value, base) 54 | /** package-level method to compute the power on any type that's Rootable. */ 55 | def pow[T](base: T, exp: T)(using r: Rootable[T]): T = r.pow(base, exp) -------------------------------------------------------------------------------- /math/src/test/scala/com/jsuereth/gl/math/math/TestVec3.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl.math 2 | 3 | import org.junit.Test 4 | import org.junit.Assert._ 5 | 6 | // TODO - test this w/ scalacheck. 7 | class TestVec3 { 8 | 9 | @Test 10 | def boolOperations(): Unit = { 11 | assertEquals(true, Vec3(true,false,false).x) 12 | assertEquals(true, Vec3(false,true,false).y) 13 | assertEquals(true, Vec3(false,false,true).z) 14 | } 15 | 16 | @Test 17 | def intOperations(): Unit = { 18 | assertEquals("length of vectors",3, Vec3(1, 1, 1).lengthSquared) 19 | assertEquals("add vectors", Vec3(-3, 1, 1), Vec3(-1, 0, 1) + Vec3(-2, 1, 0)) 20 | assertEquals("Subtract vectors", Vec3(2, 1, 0), Vec3(3, 5, -1) - Vec3(1, 4, -1)) 21 | assertEquals("Multiply vectors", Vec3(8, 16, 4), Vec3[Int](4, 8, 2) * 2) 22 | assertEquals("Dot product", 3, Vec3(1,3,-5) dot Vec3(4,-2,-1)) 23 | assertEquals("Cross product", Vec3(-3,6,-3), Vec3(2,3,4) cross Vec3(5,6,7)) 24 | assertEquals("negate", Vec3(-1,-2,-3), Vec3(1,2,3).negate) 25 | //assertEquals("normalize", Vec3(1,0,0), Vec3(8, 0, 0).normalize) 26 | // TODO - as/conversions. 27 | } 28 | 29 | @Test 30 | def floatOperations(): Unit = { 31 | val err = 0.0001f 32 | assertEquals("length of vectors",1.0f, Vec3(1.0f, 0.0f, 0.0f).lengthSquared, err) 33 | assertEquals("add vectors", Vec3(1.0f, 1.0f, 1.0f), Vec3(0.2f, 1.0f, 0.6f) + Vec3(0.8f, 0.0f, 0.4f)) 34 | // TODO - delta comparison. 35 | assertEquals("Subtract vectors", Vec3(1.0f, 1.0f, 1.0f), Vec3(1.2f, 1.35f, 0.6f) - Vec3(0.2f, 0.35f, -0.4f)) 36 | assertEquals("Multiply vectors", Vec3(2.0f, 4.0f, 1.0f), Vec3[Float](4.0f, 8.0f, 2.0f) * 0.5f) 37 | // TODO - dot 38 | // TODO - cross 39 | // TODO - negate 40 | assertEquals("normalize", Vec3(1.0f,0.0f,0.0f), Vec3(8.0f, 0.0f, 0.0f).normalize) 41 | // TODO - as/conversions. 42 | } 43 | 44 | @Test 45 | def doubleOperations(): Unit = { 46 | assertEquals("length of vectors",1.0, Vec3(1.0, 0.0, 0.0).lengthSquared, 0.01) 47 | assertEquals("add vectors", Vec3(1.0, 1.0, 1.0), Vec3(0.2, 1.0, 0.6) + Vec3(0.8, 0.0, 0.4)) 48 | assertEquals("Subtract vectors", Vec3(1.0, 1.0, 1.0), Vec3(1.2, 1.3, 0.6) - Vec3(0.2, 0.3, -0.4)) 49 | assertEquals("Multiply vectors", Vec3(2.0, 4.0, 1.0), Vec3[Double](4.0, 8.0, 2.0) * 0.5) 50 | // TODO - dot 51 | // TODO - cross 52 | // TODO - negate 53 | // TODO - normalize 54 | // TODO - as/conversions. 55 | } 56 | 57 | // TODO - Boolean vec. 58 | } 59 | -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/io/FrameBufferObject.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.io 18 | 19 | import org.lwjgl.opengl.GL30.{GL_FRAMEBUFFER,glBindFramebuffer,glGenFramebuffers,glDeleteFramebuffers} 20 | 21 | /** A frame buffer object that has been allocated in OpenGL. 22 | * You can load/store textures memory in FBOs. 23 | * Shader pipelines push data into these. 24 | */ 25 | class FrameBufferObject private (override val id: Int) extends GLBuffer { 26 | def close(): Unit = glDeleteFramebuffers(id) 27 | 28 | // TODO - Allocation of frame-objects on this buffer, specifically Samplers/Textures. 29 | 30 | def bind(): Unit = glBindFramebuffer(GL_FRAMEBUFFER, id) 31 | def unbind(): Unit = glBindFramebuffer(GL_FRAMEBUFFER, 0) 32 | 33 | // TODO - make public 34 | // TODO - common error message. 35 | /** Validates whether or not this frame bufffer is in a consistent/usable state. */ 36 | private def validate(): Unit = withBound { 37 | import org.lwjgl.opengl.GL30._ 38 | glCheckFramebufferStatus(GL_FRAMEBUFFER) match { 39 | case GL_FRAMEBUFFER_COMPLETE => () 40 | case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT => sys.error("Frame buffer incomplete attachment") 41 | case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER => sys.error("Frame buffer incomplete draw buffer") 42 | case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT => sys.error("Frame buffer incomplete missing attachment") 43 | case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER => sys.error("Frame buffer incomplete read buffer") 44 | case GL_FRAMEBUFFER_UNSUPPORTED => sys.error("Frame buffers are unsupported on this platform.") 45 | case id => sys.error(s"Unkown issue with framebuffer: $id") 46 | } 47 | } 48 | 49 | override def toString: String = s"VBO($id)" 50 | } 51 | object FrameBufferObject { 52 | /** Creates a new framebuffer we can use to allocate texture/frame objects. */ 53 | def apply(): FrameBufferObject = new FrameBufferObject(glGenFramebuffers) 54 | } -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/Trigonometry.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.math 18 | 19 | 20 | /** Accepts a value in radians and returns the sine. */ 21 | def sin[T](value: T)(using t: Trigonometry[T]): T = t.sin(value) 22 | /** Accepts a value in radians and returns the cosine. */ 23 | def cos[T](value: T)(using t: Trigonometry[T]): T = t.cos(value) 24 | /** Accepts a value in radians and returns the tangent. */ 25 | def tan[T](value: T)(using t: Trigonometry[T]): T = t.tan(value) 26 | /** Converts from Degrees to Radians for a given type. */ 27 | def angleToRadians[T](value: T)(using t: Trigonometry[T]): T = t.angleToRadians(value) 28 | 29 | 30 | /** An implementation of trigonometry functions for a type. */ 31 | trait Trigonometry[T] { 32 | /** Accepts a value in radians and returns the sine. */ 33 | def sin(value: T): T 34 | /** Accepts a value in radians and returns the cosine. */ 35 | def cos(value: T): T 36 | /** Accepts a value in radians and returns the tangent. */ 37 | def tan(value: T): T 38 | // TODO - asin,acos,atan,atan2 39 | /** Converts from Degrees to Radians for a given type. */ 40 | def angleToRadians(value: T): T 41 | } 42 | 43 | object Trigonometry 44 | // TODO - Fast lookup tables 45 | // For now we just provide java's implementation of trig. 46 | given JavaFloatTrig as Trigonometry[Float] { 47 | def sin(value: Float): Float = Math.sin(value.toDouble).toFloat 48 | def cos(value: Float): Float = Math.cos(value.toDouble).toFloat 49 | def tan(value: Float): Float = Math.tan(value.toDouble).toFloat 50 | private val conversionFactor = (scala.math.Pi / 180.0f).toFloat 51 | def angleToRadians(value: Float): Float = value * conversionFactor 52 | } 53 | given JavaDoubleTrig as Trigonometry[Double] { 54 | def sin(value: Double): Double = Math.sin(value) 55 | def cos(value: Double): Double = Math.cos(value) 56 | def tan(value: Double): Double = Math.tan(value) 57 | private val conversionFactor = (scala.math.Pi / 180.0f) 58 | def angleToRadians(value: Double): Double = value * conversionFactor 59 | } -------------------------------------------------------------------------------- /scene/src/main/scala/com/jsuereth/gl/mesh/Material.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package mesh 19 | 20 | import math._ 21 | import io._ 22 | 23 | /** Defines the baseline material we will parse out of .obj/.mtl files. We will need to manipualte this before sending into OpenGL. */ 24 | case class RawMaterial(name: String, base: BaseMaterial, textures: MaterialTextures) 25 | 26 | /** Definition of material that only includes color definitions, no texture maps. */ 27 | case class BaseMaterial( 28 | /** Ambient Color */ 29 | ka: Vec3[Float] = Vec3(0f,0f,0f), 30 | /** Diffuse Color */ 31 | kd: Vec3[Float] = Vec3(0f,0f,0f), 32 | /** Specular Color */ 33 | ks: Vec3[Float] = Vec3(0f,0f,0f), 34 | /** Specular exponent (shininess) */ 35 | ns: Float = 1f 36 | // Ignoring transparency and illumination modes. 37 | // TODO - texture maps? 38 | ) derives BufferLoadable /*, ShaderUniformLoadable */ 39 | 40 | 41 | // Defines all possible material textures we could parse. 42 | case class MaterialTextures( 43 | ka: Option[TextureReference] = None, 44 | kd: Option[TextureReference] = None, 45 | ks: Option[TextureReference] = None, 46 | ns: Option[TextureReference] = None, 47 | d: Option[TextureReference] = None, 48 | bump: Option[TextureReference] = None 49 | ) 50 | 51 | 52 | /** A referernce to a texture, including texture options. */ 53 | case class TextureReference(filename: String, options: TextureOptions) 54 | /** All the options .mtl allows for textures. We ignore almost all of this. */ 55 | case class TextureOptions( 56 | horizontalBlend: Boolean = true, 57 | verticalBlend: Boolean = true, 58 | /** Boost mip-map sharpness. */ 59 | boost: Option[Float] = None, 60 | /** Modify texture map values: base_value = brightness, gain_value = contrast. */ 61 | modifyMap: (Float,Float) = (0f, 1f), 62 | originOffset: Vec3[Float] = Vec3(0f,0f,0f), 63 | scale: Vec3[Float] = Vec3(1f,1f,1f), 64 | turbulence: Vec3[Float] = Vec3(0f,0f,0f), 65 | resolution: Option[(Int,Int)] = None, 66 | clamp: Boolean = false, 67 | bumpMultiplier: Float = 1f 68 | // TODO image channel for bump-maps. 69 | 70 | ) -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/Vec4.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package math 19 | 20 | import scala.math.Numeric 21 | import scala.reflect.ClassTag 22 | 23 | final class Vec4[T : ClassTag](private[this] val values: Array[T]) { 24 | def x: T = values(0) 25 | def y: T = values(1) 26 | def z: T = values(2) 27 | def w: T = values(3) 28 | def xy: Vec2[T] = new Vec2[T](values) 29 | def rg: Vec2[T] = new Vec2[T](values) 30 | def xyz: Vec3[T] = new Vec3[T](values) 31 | def rgb: Vec3[T] = new Vec3[T](values) 32 | 33 | 34 | def +(other: Vec4[T])(using Numeric[T]): Vec4[T] = Vec4(x+other.x, y+other.y, z+other.z, w+other.w) 35 | def -(other: Vec4[T])(using Numeric[T]): Vec4[T] = Vec4(x-other.x, y-other.y, z-other.z, w-other.w) 36 | // behaves as GLSL would, just multiplies pairs... 37 | def *(other: Vec4[T])(using Numeric[T]): Vec4[T] = Vec4(x*other.x, y*other.y, z*other.z, w*other.w) 38 | // TODO - we may need to disambiguate this from the above overload. 39 | def *(quant: T)(using Numeric[T]): Vec4[T] = Vec4(x*quant, y*quant, z*quant, w*quant) 40 | /** Computes the dot product of this vector with another. */ 41 | def dot(other: Vec4[T])(using Numeric[T]): T = 42 | (x*other.x) + (y*other.y) + (z*other.z) + (w*other.w) 43 | // TODO: cross 44 | 45 | /** the square of the length of this vector. */ 46 | def lengthSquared(using Numeric[T]): T = this dot this 47 | /** Returns the length of this vector. Requires a SQRT function. */ 48 | def length(using Rootable[T], Numeric[T]): T = sqrt(lengthSquared) 49 | 50 | /** Normalizes this vector (setting distance to 1). Note: This requires a valid SQRT function. */ 51 | def normalize(using Fractional[T], Rootable[T]): Vec4[T] = { 52 | val l = length 53 | new Vec4[T](Array((x / l), (y / l), (y / l), (w / l))) 54 | } 55 | override def toString: String = s"($x, $y, $z, $w)" 56 | } 57 | object Vec4 { 58 | def apply[T: ClassTag](x: T, y: T, z: T, w: T): Vec4[T] = new Vec4[T](Array(x,y,z, w)) 59 | def apply[T: ClassTag](xy: Vec2[T], z: T, w: T): Vec4[T] = new Vec4[T](Array(xy.x, xy.y, z, w)) 60 | def apply[T: ClassTag](xyz: Vec3[T], w: T): Vec4[T] = new Vec4[T](Array(xyz.x, xyz.y, xyz.z, w)) 61 | } 62 | -------------------------------------------------------------------------------- /scene/src/test/scala/com/jsuereth/gl/mesh/parser/TestObjParser.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl 2 | package mesh 3 | package parser 4 | 5 | import org.junit.Test 6 | import org.junit.Assert._ 7 | import math._ 8 | 9 | class TestObjParser { 10 | def stream(in: String): java.io.InputStream = 11 | java.io.ByteArrayInputStream(in.getBytes) 12 | 13 | @Test def parseSimpleMesh(): Unit = { 14 | val meshes = ObjFileParser.parse(stream("""| 15 | |o test 16 | |v 1.0 0.0 1.0 17 | |v 0.0 0.0 0.0 18 | |v 1.0 0.0 0.0 19 | |f 1 2 3 20 | |""".stripMargin('|'))) 21 | 22 | assertEquals(1, meshes.size) 23 | assertTrue(meshes.contains("test")) 24 | val mesh: Mesh3d = meshes("test") 25 | assertEquals(1, mesh.faces.size) 26 | // Texture coordinates are 0, normal is synthesized 27 | assertEquals(TriangleFace(FaceIndex(1,0,1), FaceIndex(2,0,2), FaceIndex(3,0,3)), mesh.faces.head) 28 | assertEquals(3, mesh.vertices.size) 29 | assertEquals("Parse first point", Vec3(1f,0f,1f), mesh.vertices(0)) 30 | assertEquals("Parse second point", Vec3(0f,0f,0f), mesh.vertices(1)) 31 | assertEquals("Parses third point", Vec3(1f,0f,0f), mesh.vertices(2)) 32 | assertEquals(3, mesh.normals.size) 33 | assertEquals("Calculate first normal", Vec3(0f,-1f,0f), mesh.normals(0)) 34 | assertEquals("Calculate second normal", Vec3(0f,-1f,0f), mesh.normals(1)) 35 | assertEquals("Calculate third normal", Vec3(0f,-1f,0f), mesh.normals(2)) 36 | } 37 | @Test def parseMultiObjectMesh(): Unit = { 38 | val meshes = ObjFileParser.parse(stream("""| 39 | |o test 40 | |o test2 41 | |""".stripMargin('|'))) 42 | 43 | assertEquals(2, meshes.size) 44 | assertTrue(meshes.contains("test")) 45 | assertTrue(meshes.contains("test2")) 46 | } 47 | @Test def useGivenNormals(): Unit = { 48 | val meshes = ObjFileParser.parse(stream("""| 49 | |o test 50 | |v 1.0 0.0 1.0 51 | |v 0.0 0.0 1.0 52 | |v 1.0 0.0 1.0 53 | |vn 0.0 1.0 0.0 54 | |f 1//1 2//1 3//1 55 | |""".stripMargin('|'))) 56 | assertEquals(1, meshes.size) 57 | assertTrue(meshes.contains("test")) 58 | val mesh: Mesh3d = meshes("test") 59 | assertEquals(TriangleFace(FaceIndex(1,0,1), FaceIndex(2,0,1), FaceIndex(3,0,1)), mesh.faces.head) 60 | assertEquals(3, mesh.vertices.size) 61 | assertEquals("Parse first point", Vec3(1f,0f,1f), mesh.vertices(0)) 62 | assertEquals("Parse second point", Vec3(0f,0f,1f), mesh.vertices(1)) 63 | assertEquals("Parses third point", Vec3(1f,0f,1f), mesh.vertices(2)) 64 | assertEquals(1, mesh.normals.size) 65 | assertEquals("Parse only normal", Vec3(0f,1f,0f), mesh.normals(0)) 66 | } 67 | } -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/Matrix3.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package math 19 | 20 | import scala.reflect.ClassTag 21 | 22 | // Aliases 23 | type Matrix3[T] = Matrix3x3[T] 24 | val Matrix3 = Matrix3x3 25 | 26 | /** A matrix library for floating point values, meant to be relatively efficient and the analog of GLSL matrix. */ 27 | class Matrix3x3[T : ClassTag](val values: Array[T]) { 28 | // row-column formats 29 | def m11: T = values(0) 30 | def m21: T = values(1) 31 | def m31: T = values(2) 32 | def m12: T = values(3) 33 | def m22: T = values(4) 34 | def m32: T = values(5) 35 | def m13: T = values(6) 36 | def m23: T = values(7) 37 | def m33: T = values(8) 38 | 39 | 40 | def apply(row: Int)(col: Int): T = 41 | values(row + (col*3)) 42 | 43 | def *(scale: T)(using Numeric[T]): Matrix3x3[T] = 44 | Matrix3x3(values map (_ * scale)) 45 | 46 | def *(vec: Vec3[T])(using Numeric[T]): Vec3[T] = { 47 | import vec.{x,y,z} 48 | val newX = m11*x + m12*y + m13*z 49 | val newY = m21*x + m22*y + m23*z 50 | val newZ = m31*x + m32*y + m33*z 51 | Vec3(newX, newY, newZ) 52 | } 53 | def *(other: Matrix3x3[T])(using Numeric[T]): Matrix3x3[T] = { 54 | // TODO - attempt to use Coppersmith–Winograd algorithm? 55 | // For now we do this naively, which is possibly more efficeint. 56 | val next = new Array[T](9) 57 | def idx(row: Int, col: Int): Int = row + (col*3) 58 | // TODO - Should we unroll this? 59 | for { 60 | i <- 0 until 3 61 | j <- 0 until 3 62 | } next(idx(i,j)) = (for { 63 | k <- 0 until 3 64 | } yield apply(i)(k) * other(k)(j)).sum 65 | Matrix3x3(next) 66 | } 67 | // TODO - Derive "show" or "print" across all numbers/formats once we have a shapeless release. 68 | override def toString: String = { 69 | def f(t: T): String = t.toString // TODO - use fixed-format number width... 70 | s""":|${f(m11)}|${f(m12)}|${f(m13)}| 71 | :|${f(m21)}|${f(m22)}|${f(m23)}| 72 | :|${f(m31)}|${f(m32)}|${f(m33)}|""".stripMargin(':') 73 | } 74 | } 75 | object Matrix3x3 { 76 | def identity[T: ClassTag : Numeric]: Matrix3x3[T] = 77 | new Matrix3x3(Array( 78 | one, zero, zero, 79 | zero, one, zero, 80 | zero, zero, one, 81 | )) 82 | } -------------------------------------------------------------------------------- /io/src/test/scala/com/jsuereth/gl/io/TestVaoAttribute.scala: -------------------------------------------------------------------------------- 1 | package com.jsuereth.gl 2 | package io 3 | 4 | import org.junit.Test 5 | import org.junit.Assert._ 6 | import org.lwjgl.opengl.GL11.{GL_FLOAT, GL_INT, GL_SHORT, GL_BYTE,GL_DOUBLE} 7 | 8 | case class ExamplePod(one: math.Vec2[Int], two: math.Vec3[Float]) 9 | case class ExamplePod2(one: math.Vec2[Int], two: math.Vec3[Float], three: math.Vec2[Float], four: math.Vec2[Float]) 10 | 11 | class TestSize { 12 | @Test def sizeOfPrimitive(): Unit = { 13 | assertEquals(2, sizeOf[Short]) 14 | assertEquals(4, sizeOf[Float]) 15 | assertEquals(4, sizeOf[Int]) 16 | assertEquals(8, sizeOf[Double]) 17 | assertEquals(8, sizeOf[Long]) 18 | } 19 | @Test def sizeOfGlTypes(): Unit = { 20 | assertEquals(3, sizeOf[math.Vec3[Boolean]]) 21 | assertEquals(12, sizeOf[math.Vec3[Float]]) 22 | assertEquals(36, sizeOf[math.Matrix3x3[Int]]) 23 | assertEquals(16, sizeOf[math.Vec2[Double]]) 24 | assertEquals(8*16, sizeOf[math.Matrix4x4[Long]]) 25 | assertEquals(16, sizeOf[math.Vec4[Int]]) 26 | } 27 | @Test def sizeofPod(): Unit = { 28 | assertEquals(20, sizeOf[ExamplePod]) 29 | assertEquals(36, sizeOf[ExamplePod2]) 30 | } 31 | } 32 | 33 | class TestGlType { 34 | @Test def calculateTypeOfPrimative(): Unit = { 35 | assertEquals(GL_FLOAT, glType[Float]) 36 | assertEquals(GL_INT, glType[Int]) 37 | assertEquals(GL_SHORT, glType[Short]) 38 | assertEquals(GL_BYTE, glType[Byte]) 39 | assertEquals(GL_BYTE, glType[Boolean]) 40 | assertEquals(GL_DOUBLE, glType[Double]) 41 | } 42 | @Test def calculateTypeOfGlSlTypes(): Unit = { 43 | assertEquals(GL_FLOAT, glType[math.Vec3[Float]]) 44 | assertEquals(GL_INT, glType[math.Matrix4[Int]]) 45 | } 46 | } 47 | 48 | class TestGlSize { 49 | @Test def calculateGlSizeOfPrimative(): Unit = { 50 | assertEquals(1, glSize[Float]) 51 | assertEquals(1, glSize[Int]) 52 | } 53 | @Test def calculateGlSizeOfGlSlTypes(): Unit = { 54 | assertEquals(2, glSize[math.Vec2[Boolean]]) 55 | assertEquals(3, glSize[math.Vec3[Float]]) 56 | assertEquals(4, glSize[math.Vec4[Int]]) 57 | assertEquals(9, glSize[math.Matrix3[Float]]) 58 | assertEquals(16, glSize[math.Matrix4[Int]]) 59 | } 60 | } 61 | 62 | class TestVaoAttribute { 63 | @Test def calculateVaoAttributes(): Unit = { 64 | val Array(one,two) = vaoAttributes[ExamplePod] 65 | assertEquals(one, VaoAttribute(0, 2, GL_INT, 20, 0)) 66 | assertEquals(two, VaoAttribute(1, 3, GL_FLOAT, 20, 8)) 67 | } 68 | @Test def calculateVaoAttributes2(): Unit = { 69 | val Array(one,two,three,four) = vaoAttributes[ExamplePod2] 70 | assertEquals(one, VaoAttribute(0, 2, GL_INT, 36, 0)) 71 | assertEquals(two, VaoAttribute(1, 3, GL_FLOAT, 36, 8)) 72 | assertEquals(three, VaoAttribute(2, 2, GL_FLOAT, 36, 20)) 73 | assertEquals(four, VaoAttribute(3, 2, GL_FLOAT, 36, 28)) 74 | } 75 | } -------------------------------------------------------------------------------- /scene/src/main/scala/com/jsuereth/gl/scene/Scene.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package scene 19 | 20 | import math.{Vec3, Quaternion} 21 | import collection.mutable.ArrayBuffer 22 | import mesh.Mesh3d 23 | /** 24 | * Defines the base of a renderable scene. 25 | * 26 | * A scene can be more complicated than this base trait, but here are the absolute minimal characteristics. 27 | */ 28 | trait Scene { 29 | /** The camera to use for this scene. */ 30 | def camera: Camera 31 | // TODO - figure out what kind of light abstraction we want to show off different shaders... 32 | /** Position of all lights in the scene (if multiple). */ 33 | def lights: Iterator[Vec3[Float]] 34 | /** Objects to render in the scene. */ 35 | def objectsInRenderOrder: Iterator[SceneObject] 36 | // TODO - def updateSimulation, etc.... 37 | } 38 | 39 | /** A simple static scene that contains a few objects. */ 40 | final class SimpleStaticScene( 41 | override val camera: Camera, 42 | val light: Vec3[Float], 43 | val objects: Seq[SceneObject] 44 | ) extends Scene { 45 | override def lights: Iterator[Vec3[Float]] = Iterator(light) 46 | override def objectsInRenderOrder: Iterator[SceneObject] = objects.iterator 47 | } 48 | 49 | /** Scene builder syntax. */ 50 | class SimpleStaticSceneBuilder { 51 | private var c = Camera() 52 | private var l = Vec3(0f, 10f, 0f) 53 | private val os = ArrayBuffer[SceneObject]() 54 | 55 | def light(location: Vec3[Float]): this.type = { 56 | this.l = location 57 | this 58 | } 59 | 60 | def camera(c: Camera): this.type = { 61 | this.c = c 62 | this 63 | } 64 | 65 | def add(obj: SceneObject): this.type = { 66 | this.os += obj 67 | this 68 | } 69 | def add(mesh: Mesh3d): ObjBuilder = ObjBuilder(mesh) 70 | 71 | class ObjBuilder(mesh: Mesh3d) { 72 | private[this] var p = Vec3(0f,0f,0f) 73 | private[this] var o = Quaternion.identity[Float] 74 | private[this] var s = Vec3(1f,1f,1f) 75 | def pos(pos: Vec3[Float]): this.type = { 76 | this.p = pos 77 | this 78 | } 79 | def orientation(o: Quaternion[Float]): this.type = { 80 | this.o = o 81 | this 82 | } 83 | def scale(x: Float, y: Float, z: Float): this.type = { 84 | this.s = Vec3(x,y,z) 85 | this 86 | } 87 | def done(): SimpleStaticSceneBuilder = { 88 | add(StaticSceneObject(mesh, p, o, s)) 89 | } 90 | } 91 | 92 | def done(): Scene = SimpleStaticScene(c, l, os.toSeq) 93 | } -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/texture/TextureParameter.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package texture 19 | 20 | import org.lwjgl.opengl.GL11.{ 21 | glTexParameteri, 22 | GL_TEXTURE_2D, 23 | GL_CLAMP, 24 | GL_REPEAT, 25 | GL_NEAREST, 26 | GL_LINEAR, 27 | GL_TEXTURE_WRAP_S, 28 | GL_TEXTURE_WRAP_T, 29 | GL_TEXTURE_MAG_FILTER, 30 | GL_TEXTURE_MIN_FILTER, 31 | GL_NEAREST_MIPMAP_NEAREST, 32 | GL_NEAREST_MIPMAP_LINEAR, 33 | GL_LINEAR_MIPMAP_LINEAR, 34 | GL_LINEAR_MIPMAP_NEAREST 35 | } 36 | import org.lwjgl.opengl.GL14.{ 37 | GL_MIRRORED_REPEAT 38 | } 39 | 40 | /** Controls how textures wrap in OpenGL. */ 41 | enum TextureWrapType { 42 | case ClampToEdge 43 | case MirroredRepeat 44 | case Repeat 45 | 46 | def toGL: Int = this match { 47 | case ClampToEdge => GL_CLAMP 48 | case MirroredRepeat => GL_MIRRORED_REPEAT 49 | case Repeat => GL_REPEAT 50 | } 51 | } 52 | 53 | /** Controls how we grab pixel values from textures when we don't have 1:1 mapping from point-to-texture coord. */ 54 | enum TextureFilterType { 55 | case Nearest, Linear 56 | 57 | def toGL: Int = this match { 58 | case Nearest => GL_NEAREST 59 | case Linear => GL_LINEAR 60 | } 61 | } 62 | /** Defines a parameter for an OpenGL texture, as well as the ability to set that parameter. */ 63 | enum TextureParameter { 64 | case WrapS(tpe: TextureWrapType) 65 | case WrapT(tpe: TextureWrapType) 66 | case MagFilter(tpe: TextureFilterType) 67 | case MinFilter(tpe: TextureFilterType, mipMap: Option[TextureFilterType] = None) 68 | 69 | import TextureFilterType.{Nearest,Linear} 70 | private def toMipMapTpe(tpe: TextureFilterType, mipMap: TextureFilterType): Int = (tpe, mipMap) match { 71 | case (Nearest, Nearest) => GL_NEAREST_MIPMAP_NEAREST 72 | case (Nearest, Linear) => GL_NEAREST_MIPMAP_LINEAR 73 | case (Linear, Linear) => GL_LINEAR_MIPMAP_LINEAR 74 | case (Linear, Nearest) => GL_LINEAR_MIPMAP_NEAREST 75 | } 76 | 77 | /** Call the appropriate glTextParameteri() function for this texture parameter. */ 78 | def set(target: Int = GL_TEXTURE_2D): Unit = this match { 79 | case WrapS(tpe) => glTexParameteri(target, GL_TEXTURE_WRAP_S, tpe.toGL) 80 | case WrapT(tpe) => glTexParameteri(target, GL_TEXTURE_WRAP_T, tpe.toGL) 81 | case MagFilter(tpe) => glTexParameteri(target, GL_TEXTURE_MAG_FILTER, tpe.toGL) 82 | case MinFilter(tpe, None) => glTexParameteri(target, GL_TEXTURE_MAG_FILTER, tpe.toGL) 83 | case MinFilter(tpe, Some(m)) => glTexParameteri(target, GL_TEXTURE_MIN_FILTER, toMipMapTpe(tpe, m)) 84 | } 85 | } -------------------------------------------------------------------------------- /scene/src/main/scala/com/jsuereth/gl/scene/Camera.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package scene 19 | 20 | import math.{given _, _} 21 | 22 | /** 23 | * Defines a camera for a scene. 24 | * 25 | * This class allows simple "camera" operations as if you are a first-person viewer, like "turnRight", "moveForward". 26 | * It defines itself according to three basic vectors: eye position, up direction and focus position. 27 | * Using this when rendering requires converting to a ViewMatrix and loading into a shader. 28 | */ 29 | class Camera { 30 | // TODO - better defaults... 31 | private var eye = Vec3[Float](0.0f, 0.0f, -10f) // Location of the camera 32 | private var up = Vec3[Float](0.0f, 1.0f, 0.0f) // The up vector. 33 | private var focus = Vec3[Float](0.0f, 0.0f, 0.0f) // Center of the camera foucs 34 | /** Returns the current position of the eye. */ 35 | def eyePosition: Vec3[Float] = eye 36 | 37 | // The direction we're looking... 38 | private def lookDirection = (focus - eye).normalize 39 | // The direction to the right. 40 | private def rightDirection = (lookDirection cross up).normalize 41 | // TODO - Calculate the right/left direction 42 | 43 | def reset( eye: Vec3[Float] = Vec3(-0.5f, 5.0f, -0.5f), 44 | up: Vec3[Float] = Vec3(0.0f, 1.0f, 0.0f), 45 | focus: Vec3[Float] = Vec3(0.0f, 0.0f, 0.0f)): Unit = { 46 | this.eye = eye 47 | this.up = up 48 | this.focus = focus 49 | } 50 | 51 | def moveRight(amount: Float): Unit = { 52 | val mover = rightDirection * amount 53 | eye += mover 54 | focus += mover 55 | println(this) 56 | } 57 | 58 | def turnRight(amount: Float): Unit = { 59 | val rot = Quaternion.rotation(-amount, up) 60 | focus = eye + (rot * (focus-eye)) 61 | println(this) 62 | } 63 | 64 | def turnUp(amount: Float): Unit = { 65 | val rot = Quaternion.rotation(amount, rightDirection) 66 | focus = eye + (rot * (focus-eye)) 67 | println(this) 68 | } 69 | 70 | 71 | def moveForward(amount: Float): Unit = { 72 | val mover = lookDirection * amount 73 | focus += mover 74 | eye += mover 75 | println(this) 76 | } 77 | 78 | def moveUp(amount: Float): Unit = { 79 | val mover = up * amount 80 | eye += mover 81 | focus += mover 82 | println(this) 83 | } 84 | 85 | def zoom(amount: Float): Unit = { 86 | // TODO - Scale this differently... 87 | focus = focus + (lookDirection * amount) 88 | } 89 | 90 | def viewMatrix: Matrix4x4[Float] = Matrix4.lookAt(focus, eye, up) 91 | 92 | override def toString: String = 93 | s"""|Camera Positions 94 | |- eye: $eye 95 | |- focus: $focus 96 | |- up: $up""".stripMargin 97 | } -------------------------------------------------------------------------------- /example/src/main/scala/CartonShader.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.jsuereth.gl.shaders._ 18 | import com.jsuereth.gl.math.{given _, _} 19 | import com.jsuereth.gl.texture.Texture2D 20 | import com.jsuereth.gl.io.ShaderUniformLoadable 21 | 22 | case class WorldData(light: Vec3[Float], eye: Vec3[Float], view: Matrix4[Float], projection: Matrix4[Float]) derives ShaderUniformLoadable 23 | 24 | object CartoonShader extends DslShaderProgram { 25 | // Hack to get structures to work. OpenGL spec + reality may not line up on how to look up structure ids. Need to rethink 26 | // ShaderUniformLoadable class... 27 | val world = Uniform[WorldData]() 28 | val modelMatrix = Uniform[Matrix4[Float]]() 29 | val materialShininess = Uniform[Float]() 30 | val materialKd = Uniform[Float]() 31 | val materialKs = Uniform[Float]() 32 | // Textures in lighting model. 33 | val materialKdTexture = Uniform[Texture2D]() 34 | 35 | val (vertexShaderCode, fragmentShaderCode) = defineShaders { 36 | val inPosition = Input[Vec3[Float]](location=0) 37 | val inNormal = Input[Vec3[Float]](location=1) 38 | val texPosition = Input[Vec2[Float]](location=2) 39 | // TODO - convert to matrix3... 40 | val worldPos = (modelMatrix() * Vec4(inPosition, 1)).xyz 41 | val worldNormal = (modelMatrix() * Vec4(inNormal, 0)).xyz 42 | // Note: We have to do this dance to feed this into fragment shader. 43 | val texCoord: Vec2[Float] = texPosition 44 | glPosition(world().projection * world().view * modelMatrix() * Vec4(inPosition, 1)) 45 | // Fragment shader 46 | fragmentShader { 47 | val L = (world().light - worldPos).normalize 48 | val N = worldNormal.normalize 49 | val lambertian = Math.max(L.dot(N), 0.0f).toFloat 50 | val V = (world().eye - worldPos).normalize 51 | val H = (L + V).normalize 52 | val diffuse = materialKd() * lambertian 53 | val specular = 54 | if (lambertian > 0.0f) materialKs() * Math.pow(Math.max(0, H.dot(N)).toFloat, materialShininess()).toFloat 55 | else 0.0f 56 | //Black color if dot product is smaller than 0.3 57 | //else keep the same colors 58 | val edgeDetection = if (V.dot(worldNormal) > 0.3f) 1f else 0.7f 59 | val texColor = materialKdTexture().texture(texCoord).xyz 60 | // TODO - add ambient light? 61 | val light = (((texColor*diffuse) + (texColor*specular)) * edgeDetection) 62 | Output("color", 0, Vec4(light, 1f)) 63 | } 64 | } 65 | } 66 | 67 | 68 | object SimpleShader extends DslShaderProgram { 69 | val (vertexShaderCode, fragmentShaderCode) = defineShaders { 70 | val position = Input[Vec3[Float]](location = 0) 71 | glPosition(Vec4(position, 1.0f)) 72 | fragmentShader { 73 | Output("color", 0, Vec4(0.0f, 0.5f, 0.5f, 1.0f)) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/io/ActiveTextures.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package io 19 | 20 | import org.lwjgl.opengl.GL11.glGetInteger 21 | import org.lwjgl.opengl.GL13.{ 22 | GL_TEXTURE0, 23 | glActiveTexture 24 | } 25 | import org.lwjgl.opengl.GL20.{ 26 | glUniform1i, 27 | GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 28 | } 29 | import scala.collection.mutable.ArrayBuffer 30 | 31 | /** A type representing an ActiveTextureIndex. Normally in opengl these are GL_TEXTURE* enums. */ 32 | opaque type ActiveTextureIndex = Int 33 | 34 | /** Convenience method to turn an index into a GL_TEXTUREN enuma nd call glActiveTexture. */ 35 | def activate(idx: ActiveTextureIndex): Unit = glActiveTexture(GL_TEXTURE0 + idx) 36 | /** Convenience method to bind the index of a texture to a uniform variable. */ 37 | def loadTextureUniform(location: Int, idx: ActiveTextureIndex): Unit = glUniform1i(location, unwrap(idx)) 38 | /** The maximum value of active indicies. */ 39 | def MaxActiveIndex: ActiveTextureIndex = glGetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) 40 | 41 | // Internal helper to lift things into/out of opque type. 42 | private def unwrap(idx: ActiveTextureIndex): Int = idx 43 | private def wrap(idx: Int): ActiveTextureIndex = idx 44 | 45 | /** This interfaces describes which textures are bound, and gives access to binding IDs 46 | * that can be used to bind more textures. 47 | * 48 | * This is a mutable interface, as we expect it to be passe dthrough shader loadable context. 49 | */ 50 | trait ActiveTextures { 51 | /** Grabs the next available texture index. */ 52 | def acquire(): ActiveTextureIndex 53 | /** Returns a texture index in this call. */ 54 | def release(idx: ActiveTextureIndex): Unit 55 | 56 | /** Acquires an active texture for a task,t hen releases it. */ 57 | inline def withNextActiveIndex[A](f: ActiveTextureIndex => A): A = { 58 | val idx = acquire() 59 | try f(idx) 60 | finally release(idx) 61 | } 62 | /** Create a new "record" of what textures were used. */ 63 | def push(): Unit 64 | /** Automatically release all textures used since last push. */ 65 | def pop(): Unit 66 | } 67 | object ActiveTextures { 68 | def apply(maxIndex: Int = unwrap(MaxActiveIndex)): ActiveTextures = SimpleActiveTextures(maxIndex) 69 | } 70 | 71 | private class SimpleActiveTextures(maxIndex: Int) extends ActiveTextures { 72 | private var depth: Int = 0 73 | private val MaxDepth = 100 74 | private val used: ArrayBuffer[Int] = (0 until maxIndex).map(_ => 100).to(ArrayBuffer) 75 | /** Create a new "record" of what textures were used. */ 76 | def push(): Unit = depth = Math.max(MaxDepth, depth+1) 77 | /** Open up all textures for usage from the last "pop" */ 78 | def pop(): Unit = depth = Math.max(0, depth-1) 79 | def release(idx: ActiveTextureIndex): Unit = used(unwrap(idx)) = MaxDepth 80 | // TODO - ring buffer impl, something faster? 81 | def acquire(): ActiveTextureIndex = 82 | used.iterator.zipWithIndex.find((value, idx) => value > depth) match { 83 | case Some((_, idx)) => 84 | used(idx) = depth 85 | wrap(idx) 86 | case None => throw RuntimeException("No more textures available!") 87 | } 88 | } -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/Vec3.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package math 19 | 20 | import scala.math.Numeric 21 | import scala.reflect.ClassTag 22 | 23 | // TODO - look into making this an opaque type w/ wrappers. We give up == + toString... 24 | // TODO - we probably need to provide our own set of numeric classes for this use case. 25 | final class Vec3[T : ClassTag](private[this] val values: Array[T]) { 26 | /** The x coordinate value. */ 27 | def x: T = values(0) 28 | /** The y coordinate value. */ 29 | def y: T = values(1) 30 | /** The z coordinate value. */ 31 | def z: T = values(2) 32 | 33 | /** Piecewise sum this vector with another. */ 34 | def +(other: Vec3[T])(using Numeric[T]): Vec3[T] = Vec3(x+other.x, y+other.y, z+other.z) 35 | /** Piecewise subtract this vector with another. */ 36 | def -(other: Vec3[T])(using Numeric[T]): Vec3[T] = Vec3(x-other.x, y-other.y, z-other.z) 37 | /** Piecewise multiple this vector with another. */ 38 | def *(other: Vec3[T])(using Numeric[T]): Vec3[T] = Vec3[T](x*other.x, y*other.y, z*other.z) 39 | /** Multiple a given value against each element of the vector. */ 40 | def *(quant: T)(using Numeric[T]): Vec3[T] = Vec3(x*quant, y*quant, z*quant) 41 | 42 | 43 | /** Negates the value of this vector. */ 44 | def negate(using Numeric[T]): Vec3[T] = Vec3(-x,-y,-z) 45 | 46 | /** Computes the dot product of this vector with another. */ 47 | def dot(other: Vec3[T])(using Numeric[T]): T = 48 | (x*other.x) + (y*other.y) + (z*other.z) 49 | 50 | /** Cross product with another vector. */ 51 | def cross(other: Vec3[T])(using Numeric[T]): Vec3[T] = 52 | Vec3( 53 | (y*other.z) - (z*other.y), 54 | (z*other.x) - (x*other.z), 55 | (x*other.y) - (y*other.x) 56 | ) 57 | 58 | /** Returns the square of the length of this vector. More efficient to compute than length. */ 59 | def lengthSquared(using Numeric[T]): T = this dot this 60 | /** Returns the length of this vector. Requires a SQRT function. */ 61 | def length(using Rootable[T], Numeric[T]): T = sqrt(lengthSquared) 62 | 63 | /** Normalizes this vector. Note: This requires a valid SQRT function. */ 64 | def normalize(using Fractional[T], Rootable[T]): Vec3[T] = 65 | new Vec3[T](Array((x / length), (y / length), (z / length))) 66 | 67 | 68 | // GLSL compat 69 | def xy: Vec2[T] = new Vec2[T](values) 70 | def xyz: Vec3[T] = this 71 | def rg: Vec2[T] = new Vec2[T](values) 72 | def rgb: Vec3[T] = this 73 | 74 | 75 | // TODO - Convert/cast between types. Should piece-wise convert. 76 | def as[U : ClassTag]: Vec3[U] = ??? 77 | 78 | override def toString: String = s"($x,$y,$z)" 79 | override def equals(o: Any): Boolean = 80 | o match { 81 | case other: Vec3[T] => (x == other.x) && (y == other.y) && (z == other.z) 82 | case _ => false 83 | } 84 | 85 | // TODO - hashcode the contents of the array. 86 | override def hashCode(): Int = super.hashCode() 87 | } 88 | object Vec3 { 89 | def apply[T: ClassTag](x: T, y: T, z: T): Vec3[T] = new Vec3[T](Array(x,y,z)) 90 | def apply[T: ClassTag](xy: Vec2[T], z: T): Vec3[T] = new Vec3[T](Array(xy.x, xy.y, z)) 91 | // TODO - type-generic versions? 92 | def zero = Vec3(0,0,0) 93 | def up = Vec3(0,1,0) 94 | def down = Vec3(0,-1,0) 95 | def right = Vec3(1,0,0) 96 | def left = Vec3(-1,0,0) 97 | def forward = Vec3(0,0,-1) 98 | def back = Vec3(0,0,1) 99 | } -------------------------------------------------------------------------------- /shader/src/test/scala/com/jsuereth/gl/shaders/TestShader.scala: -------------------------------------------------------------------------------- 1 | import org.junit.Test 2 | import org.junit.Assert._ 3 | 4 | import com.jsuereth.gl.shaders._ 5 | import com.jsuereth.gl.math._ 6 | import com.jsuereth.gl.io._ 7 | import com.jsuereth.gl.test.assertCleanEquals 8 | 9 | /** This is our target syntax. */ 10 | // Attempt at cel-shading 11 | import com.jsuereth.gl.math.{given _, _} 12 | object ExampleCartoonShader extends DslShaderProgram { 13 | // Used for vertex shader 14 | val modelMatrix = Uniform[Matrix4[Float]]() 15 | val viewMatrix = Uniform[Matrix4[Float]]() 16 | val projectionMatrix = Uniform[Matrix4[Float]]() 17 | 18 | // User for pixel shader / lighting model 19 | val lightPosition = Uniform[Vec3[Float]]() 20 | val eyePosition = Uniform[Vec3[Float]]() 21 | val materialShininess = Uniform[Int]() 22 | val materialKd = Uniform[Float]() 23 | val materialKs = Uniform[Float]() 24 | 25 | val (vertexShaderCode, fragmentShaderCode) = defineShaders { 26 | val inPosition = Input[Vec3[Float]](location=0) 27 | val inNormal = Input[Vec3[Float]](location=1) 28 | // TODO - convert to matrix3... 29 | val worldPos = (modelMatrix() * Vec4(inPosition, 1)).xyz 30 | val worldNormal = (modelMatrix() * Vec4(inNormal, 0)).xyz 31 | 32 | glPosition(projectionMatrix() * viewMatrix() * modelMatrix() * Vec4(inPosition, 1)) 33 | // Fragment shader 34 | fragmentShader { 35 | val L = (lightPosition() - worldPos).normalize 36 | val V = (eyePosition() - worldPos).normalize 37 | val H = (L + V).normalize 38 | val diffuse = materialKd() * Math.max(0, L.dot(worldNormal)) 39 | val specular = 40 | if (L.dot(worldNormal) > 0.0f) materialKs() * Math.pow(Math.max(0, H.dot(worldNormal)).toFloat, materialShininess()).toFloat 41 | else 0.0f 42 | //Black color if dot product is smaller than 0.3 43 | //else keep the same colors 44 | val edgeDetection = if (V.dot(worldNormal) > 0.3f) 1f else 0f 45 | val light = edgeDetection * (diffuse + specular) 46 | Output("color", 0, Vec4(light, light, light, 1f)) 47 | } 48 | } 49 | } 50 | 51 | 52 | class TestSimpleShaderGeneration { 53 | @Test def extractCartoonVertexShader(): Unit = { 54 | assertCleanEquals( 55 | """#version 300 es 56 | 57 | precision highp float; 58 | precision highp int; 59 | 60 | out vec3 worldPos; 61 | out vec3 worldNormal; 62 | layout (location = 0) in vec3 inPosition; 63 | layout (location = 1) in vec3 inNormal; 64 | uniform mat4 modelMatrix; 65 | uniform mat4 projectionMatrix; 66 | uniform mat4 viewMatrix; 67 | void main() { 68 | worldPos = ((modelMatrix * vec4(inPosition,1.0))).xyz; 69 | worldNormal = ((modelMatrix * vec4(inNormal,0.0))).xyz; 70 | gl_Position = (((projectionMatrix * viewMatrix) * modelMatrix) * vec4(inPosition,1.0)); 71 | }""", ExampleCartoonShader.vertexShaderCode) 72 | } 73 | @Test def extractCartoonFragmentShader(): Unit = { 74 | assertCleanEquals( 75 | """#version 300 es 76 | 77 | precision highp float; 78 | precision highp int; 79 | 80 | in vec3 worldPos; 81 | in vec3 worldNormal; 82 | uniform vec3 lightPosition; 83 | uniform vec3 eyePosition; 84 | uniform float materialKd; 85 | uniform float materialKs; 86 | uniform int materialShininess; 87 | layout (location = 0) out vec4 color; 88 | void main() { 89 | vec3 L = normalize((lightPosition - worldPos)); 90 | vec3 V = normalize((eyePosition - worldPos)); 91 | vec3 H = normalize((L + V)); 92 | float diffuse = (materialKd * max(0.0,dot(L,worldNormal))); 93 | float specular = ((dot(L,worldNormal) > 0.0)) ? ((materialKs * pow(max(0.0,dot(H,worldNormal)),materialShininess))) : (0.0); 94 | float edgeDetection = ((dot(V,worldNormal) > 0.3)) ? (1.0) : (0.0); 95 | float light = (edgeDetection * (diffuse + specular)); 96 | color = vec4(light,light,light,1.0); 97 | }""", ExampleCartoonShader.fragmentShaderCode) 98 | } 99 | 100 | @Test def extractsUniformNames(): Unit = { 101 | assertEquals("modelMatrix", ExampleCartoonShader.modelMatrix.name) 102 | assertEquals("lightPosition", ExampleCartoonShader.lightPosition.name) 103 | } 104 | } -------------------------------------------------------------------------------- /shader/src/main/scala/com/jsuereth/gl/shaders/codegen/ast.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.shaders.codegen 18 | 19 | case class Ast(decls: Seq[Declaration], version: String = "300 es") { 20 | // TODO - which version do we target? 21 | // TODO - Allow specifying default precision for opengl ES 22 | def toProgramString: String = 23 | 24 | s"""|#version $version 25 | | 26 | |precision highp float; 27 | |precision highp int; 28 | | 29 | |${decls.map(_.toProgramString).mkString("\n")}""".stripMargin('|') 30 | } 31 | 32 | // TODO - a better encoding of variables from GLSL 33 | /** Declarations available in GLSL at the top-level. */ 34 | enum Declaration { 35 | case Uniform(name: String, tpe: String) 36 | // TODO - better layout controls 37 | case Input(name: String, tpe: String, location: Option[Int] = None) 38 | case Output(name: String, tpe: String, location: Option[Int] = None) 39 | // TODO - method with args... 40 | case Method(name: String, tpe: String, block: Seq[Statement]) 41 | /** The definitiion of a struct. */ 42 | case Struct(name: String, members: Seq[StructMember]) 43 | 44 | def toProgramString: String = this match { 45 | case Uniform(name, tpe) => s"uniform $tpe $name;" 46 | case Input(name, tpe, Some(location)) => s"layout (location = $location) in $tpe $name;" 47 | case Input(name, tpe, None) => s"in $tpe $name;" 48 | case Output(name, tpe, Some(location)) => s"layout (location = $location) out $tpe $name;" 49 | case Output(name, tpe, None) => s"out $tpe $name;" 50 | case Method(name, tpe, block) => s"$tpe $name() {\n ${block.map(_.toProgramString).mkString("\n ")}\n}" 51 | case Struct(name, members) => s"struct $name {\n ${members.map(_.toProgramString).mkString("\n ")}\n};" 52 | } 53 | } 54 | 55 | /** A member of a structure. */ 56 | case class StructMember(name: String, tpe: String) { 57 | def toProgramString: String = s"$tpe $name;" 58 | } 59 | 60 | /** Statements in the GLSL language. */ 61 | enum Statement { 62 | case LocalVariable(name: String, tpe: String, initialValue: Option[Expr]) 63 | case Effect(expr: Expr) 64 | case Assign(ref: String, value: Expr) 65 | 66 | def toProgramString: String = this match { 67 | // Here is a hack to remove empty statements we use as placehoders.... 68 | case Effect(expr) => s"${expr.toProgramString};" 69 | case LocalVariable(name, tpe, None) => s"$tpe $name;" 70 | case LocalVariable(name, tpe, Some(expr)) => s"$tpe $name = ${expr.toProgramString};" 71 | case Assign(ref, value) => s"$ref = ${value.toProgramString};" 72 | } 73 | } 74 | 75 | // TODO - encode the valid range of expressions in GLSL 76 | /** Valid expressions (rvalues) in GLSL */ 77 | enum Expr { 78 | case MethodCall(name: String, args: Seq[Expr]) 79 | case Id(name: String) 80 | case Terenary(cond: Expr, lhs: Expr, rhs: Expr) 81 | case Operator(name: String, lhs: Expr, rhs: Expr) 82 | case Select(lhs: Expr, property: String) 83 | case Negate(expr: Expr) 84 | 85 | 86 | def toProgramString: String = this match { 87 | case MethodCall(name, args) => s"$name(${args.map(_.toProgramString).mkString("", ",", "")})" 88 | case Id(name) => name 89 | case Operator(name, lhs, rhs) => s"(${lhs.toProgramString} $name ${rhs.toProgramString})" 90 | case Negate(expr) => s"-(${expr.toProgramString})" 91 | case Terenary(cond, lhs, rhs) => s"(${cond.toProgramString}) ? (${lhs.toProgramString}) : (${rhs.toProgramString})" 92 | case Select(lhs, rhs) => s"(${lhs.toProgramString}).$rhs" 93 | } 94 | } -------------------------------------------------------------------------------- /scene/src/main/scala/com/jsuereth/gl/mesh/parser/MtlParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package mesh 19 | package parser 20 | 21 | 22 | import java.io.{File, InputStream} 23 | import math.{Vec2,Vec3} 24 | 25 | object MtlFileParser { 26 | def parse(in: File): Map[String,RawMaterial] = 27 | parse(java.io.FileInputStream(in)) 28 | def parse(in: InputStream): Map[String,RawMaterial] = { 29 | val tmp = MtlFileParser() 30 | tmp.parse(in) 31 | } 32 | } 33 | class MtlFileParser { 34 | private var currentParser: Option[(String, MtlParser)] = None 35 | private var materials = collection.mutable.Map[String, RawMaterial]() 36 | 37 | 38 | def parse(in: InputStream): Map[String,RawMaterial] = { 39 | try readStream(in) 40 | finally in.close() 41 | materials.toMap 42 | } 43 | private def readStream(in: java.io.InputStream): Unit = { 44 | val buffer = java.io.BufferedReader(java.io.InputStreamReader(in)) 45 | def read(): Unit = 46 | buffer.readLine match { 47 | case null => () 48 | case line => 49 | readLine(line) 50 | read() 51 | } 52 | read() 53 | cleanSubParser() 54 | } 55 | private def readLine(line: String): Unit = 56 | line match { 57 | case ObjLine("#", _) | "" => // Ignore comments. 58 | case ObjLine("newmtl", name +: Nil) => 59 | cleanSubParser() 60 | currentParser = Some((name, MtlParser(name))) 61 | case msg => 62 | currentParser match { 63 | case Some((_, parser)) => parser.readLine(msg) 64 | case None => System.err.println(s"Could not read line in mtllib: $msg") // TODO - better errors. 65 | } 66 | } 67 | private def cleanSubParser(): Unit = 68 | currentParser match { 69 | case Some((name, parser)) => materials.put(name, parser.mtl) 70 | case _ => None 71 | } 72 | } 73 | 74 | class MtlParser(name: String) { 75 | private var base = BaseMaterial() 76 | private var textures = MaterialTextures() 77 | def readLine(line: String): Unit = 78 | line match { 79 | case ObjLine("Ka", P3f(x,y,z)) => base = base.copy(ka = Vec3(x,y,z)) 80 | case ObjLine("Kd", P3f(x,y,z)) => base = base.copy(kd = Vec3(x,y,z)) 81 | case ObjLine("Ks", P3f(x,y,z)) => base = base.copy(ks = Vec3(x,y,z)) 82 | case ObjLine("Ns", Seq(Fl(value))) => base = base.copy(ns = value) 83 | case ObjLine("map_Ka", TextureRef(ref)) => textures = textures.copy(ka = Some(ref)) 84 | case ObjLine("map_Kd", TextureRef(ref)) => textures = textures.copy(kd = Some(ref)) 85 | case ObjLine("map_Ks", TextureRef(ref)) => textures = textures.copy(ks = Some(ref)) 86 | case ObjLine("map_Ns", TextureRef(ref)) => textures = textures.copy(ns = Some(ref)) 87 | case ObjLine("map_d", TextureRef(ref)) => textures = textures.copy(d = Some(ref)) 88 | case ObjLine("map_bump", TextureRef(ref)) => textures = textures.copy(bump = Some(ref)) 89 | case ObjLine("bump", TextureRef(ref)) => textures = textures.copy(bump = Some(ref)) 90 | case msg => System.err.println(s"Could not read line in mtl: $msg") // TODO - better errors. 91 | } 92 | 93 | def mtl: RawMaterial = RawMaterial(name, base, textures) 94 | } 95 | 96 | object TextureRef { 97 | def unapply(values: Seq[String]): Option[TextureReference] = 98 | values match { 99 | case Seq(f) => Some(TextureReference(f, TextureOptions())) 100 | // TODO - handle texture options. 101 | case _ => None 102 | } 103 | } -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/texture/Texture.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package texture 19 | 20 | import io._ 21 | 22 | import org.lwjgl.opengl.GL11.{ 23 | glEnable, 24 | GL_FLOAT, 25 | glBindTexture, 26 | glGenTextures, 27 | glDeleteTextures, 28 | GL_TEXTURE_2D, 29 | GL_TEXTURE_1D, 30 | glPixelStorei, 31 | GL_UNPACK_ALIGNMENT, 32 | GL_RGBA, 33 | GL_RGBA8, 34 | GL_UNSIGNED_BYTE, 35 | glTexImage2D 36 | } 37 | import org.lwjgl.opengl.GL12.{ 38 | GL_TEXTURE_3D 39 | } 40 | import org.lwjgl.opengl.GL30.{ 41 | glGenerateMipmap 42 | } 43 | /** Represents a texture loaded into OpenGL. */ 44 | sealed trait Texture extends io.GLBuffer { 45 | /** The id of the texture in OpenGL. */ 46 | def id: Int 47 | 48 | override def close(): Unit = glDeleteTextures(id) 49 | } 50 | object Texture { 51 | // TODO - loading methods for various formats. 52 | // TODO - mechanism to load into/out of frame buffers... 53 | import TextureParameter._ 54 | import TextureFilterType.{Linear} 55 | // Give up on generic texture loading, an,d just do one for PNGs. 56 | def loadImage(source: java.io.InputStream, 57 | params: Seq[TextureParameter] = Seq(MinFilter(Linear), MagFilter(Linear)), 58 | genMipMap: Boolean = true): Texture2D = { 59 | // Double give-up, we use AWT for now... 60 | val img = javax.imageio.ImageIO.read(source) 61 | val pixels = new Array[Int](img.getHeight*img.getWidth) 62 | img.getRGB(0, 0, img.getWidth, img.getHeight, pixels, 0, img.getWidth) 63 | // Load RGBA format 64 | val buf = java.nio.ByteBuffer.allocateDirect(img.getWidth*img.getHeight*4) 65 | // We invert the y axis for OpenGL. 66 | for { 67 | y <- (0 until img.getHeight).reverse 68 | x <- 0 until img.getWidth 69 | pixel = pixels(y*img.getWidth + x) 70 | } { 71 | // RGBA 72 | buf.put(((pixel >> 16) & 0xFF).toByte) 73 | buf.put(((pixel >> 8) & 0xFF).toByte) 74 | buf.put((pixel & 0xFF).toByte) 75 | buf.put(((pixel >> 24) & 0xFF).toByte) 76 | } 77 | buf.flip() 78 | val id = glGenTextures() 79 | glBindTexture(GL_TEXTURE_2D, id) 80 | import org.lwjgl.opengl.GL11._ 81 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 82 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 83 | // TODO - params are not doing the right thing. 84 | //params.foreach(_.set(GL_TEXTURE_2D)) 85 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img.getWidth, img.getHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf) 86 | glBindTexture(GL_TEXTURE_2D, 0); 87 | Texture2D(id) 88 | } 89 | 90 | // TODO - is this useful? 91 | inline def textureType[T]: Int = inline compiletime.erasedValue[T] match { 92 | case _: Texture1D => GL_TEXTURE_1D 93 | case _: Texture2D => GL_TEXTURE_2D 94 | case _: Texture3D => GL_TEXTURE_3D 95 | case _ => compiletime.error("Not a valid texture type!") 96 | } 97 | } 98 | 99 | 100 | /** A reference to a one-dimensional texture. */ 101 | case class Texture1D(id: Int) extends Texture { 102 | def bind(): Unit = glBindTexture(GL_TEXTURE_1D, id) 103 | def unbind(): Unit = glBindTexture(GL_TEXTURE_1D, 0) 104 | } 105 | 106 | /** A reference to a two dimensional texture. */ 107 | case class Texture2D(id: Int) extends Texture { 108 | def bind(): Unit = glBindTexture(GL_TEXTURE_2D, id) 109 | def unbind(): Unit = glBindTexture(GL_TEXTURE_2D, 0) 110 | } 111 | 112 | /** A reference to a three dimensional texture. */ 113 | case class Texture3D(id: Int) extends Texture { 114 | def bind(): Unit = glBindTexture(GL_TEXTURE_3D, id) 115 | def unbind(): Unit = glBindTexture(GL_TEXTURE_3D, 0) 116 | } 117 | 118 | -------------------------------------------------------------------------------- /shader/src/main/scala/com/jsuereth/gl/shaders/DslShaderProgram.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.shaders 18 | 19 | import com.jsuereth.gl.math._ 20 | import com.jsuereth.gl.texture.{Texture2D} 21 | 22 | import scala.quoted._ 23 | import scala.annotation.compileTimeOnly 24 | 25 | import com.jsuereth.gl.io.ShaderUniformLoadable 26 | import org.lwjgl.system.MemoryStack 27 | 28 | /** this object contains our helper macros for this DSL. */ 29 | object DslShaderProgram { 30 | def defineShadersImpl[T](f: Expr[T])(using ctx: QuoteContext): Expr[(String,String)] = { 31 | import ctx.tasty._ 32 | val helpers = codegen.Convertors[ctx.tasty.type](ctx.tasty) 33 | val (vert,frag) = helpers.convert(f.unseal) 34 | // TODO - we need to detect cross/references between shaders and lift into input/output variables. 35 | '{Tuple2(${Expr(vert.toProgramString)}, ${Expr(frag.toProgramString)})} 36 | } 37 | 38 | def valNameImpl(using ctx: QuoteContext): Expr[String] = { 39 | import ctx.tasty._ 40 | // QUick hack to look up the owner hierarhcy for a legit name... 41 | def findNonMacroSymbol(o: Symbol): Symbol = 42 | if (o.name == "macro") findNonMacroSymbol(o.owner) 43 | else o 44 | // TODO - Detect a structure and look up its first uniform member as the location, i.e. make its name be 45 | // that member, which is hacky, but an "ok" workaround. 46 | findNonMacroSymbol(ctx.tasty.rootContext.owner) match { 47 | case o: Symbol if o.isValDef => Expr(o.name) 48 | case _ => 49 | ctx.error("Uniform() must be directly assigned to a val") 50 | Expr("") 51 | } 52 | } 53 | inline def valName: String = ${valNameImpl} 54 | def valNameOrFirstStructNameImpl[T](using ctx: QuoteContext, tpe: Type[T]): Expr[String] = { 55 | import ctx.tasty._ 56 | val helpers = codegen.Convertors[ctx.tasty.type](ctx.tasty) 57 | if (helpers.isStructType(tpe.unseal.tpe)) { 58 | '{${valNameImpl} + "." + ${Expr(helpers.firstStructMemberName(tpe.unseal.tpe).get)}} 59 | } else valNameImpl 60 | } 61 | inline def valOrStructName[T]: String = ${valNameOrFirstStructNameImpl[T]} 62 | 63 | def testStructDefImpl[T](using ctx: QuoteContext, tpe: Type[T]): Expr[String] = { 64 | import ctx.tasty._ 65 | val helpers = codegen.Convertors[ctx.tasty.type](ctx.tasty) 66 | Expr(helpers.toStructDefinition(tpe.unseal.tpe).map(_.toProgramString).toString) 67 | } 68 | inline def testStructDef[T] = ${testStructDefImpl[T]} 69 | } 70 | abstract class DslShaderProgram extends BasicShaderProgram { 71 | 72 | // TODO - we'd also like to implicit-match either ShaderUniformLoadable *or* OpaqueGlslType. 73 | inline def Uniform[T : ShaderUniformLoadable](): Uniform[T] = 74 | MyUniform[T](DslShaderProgram.valOrStructName[T]) 75 | 76 | // API for defining shaders... 77 | 78 | inline def defineShaders[T](inline f: => T): (String, String) = ${DslShaderProgram.defineShadersImpl('f)} 79 | inline def fragmentShader[T](inline f: => T): Unit = f 80 | 81 | 82 | // COMPILE-TIME ONLY METHODS. TODO - move this into some kind of API file... 83 | // Allow DslShaders to access uniform values, but force this call to be within DSL. 84 | @compileTimeOnly("Can only access a uniform within a Shader.") 85 | inline def [T](c: Uniform[T]) apply(): T = ??? 86 | @compileTimeOnly("Can only define input within a shader.") 87 | inline def Input[T](location: Int): T = ??? 88 | @compileTimeOnly("Can only define output within a shader.") 89 | inline def Output[T](name: String, location: Int, value: T): Unit = ??? 90 | @compileTimeOnly("Can only define glPosition within a shader.") 91 | inline def glPosition[T](vec: Vec4[T]): Unit = ??? 92 | /** Samples a texture at a coordinate, pulling the color back. */ 93 | @compileTimeOnly("Textures can only be sampled within a fragment shader.") 94 | inline def (c: Texture2D) texture(coord: Vec2[Float]): Vec4[Float] = ??? 95 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShadySide - OpenGL hackery in Scala 3.0 2 | 3 | This project aims to run Scala 3.0 through the ringer via producing 4 | an OpenGL DSL on top of LWJGL. The API should allow the following: 5 | 6 | - GLSL shaders defined fully within Scala, staged and commpiled on GPU. 7 | - "Fast" vector/matrix library for canonical graphics/gpu applications to 8 | pair w/ the GLSL shader API. 9 | - Easy support for stack-allocation hook of LWJGL 10 | - No attempt, at ALL, to be a "game engine". 11 | - Modest attempt at a linear algebra library, to be hacked on 12 | when it seems fun. 13 | 14 | This is a direct re-write of a personal project from 2013 where I was first learning OpenGL in Scala. As such, it retains many mistakes 15 | and poor assumptions. 16 | 17 | This project is split into several components: 18 | 19 | ## Linear Algebra 20 | A linear algebra library that provides missing types required in OpenGL shader language, specifically: 21 | 22 | * UInt 23 | * Vec2[T] 24 | * Vec3[T] 25 | * Vec4[T] 26 | * Matrix3x3[T] 27 | * Matrix4x4[T] 28 | 29 | In addition to baseline types, this library also provides some abstractions around types to be usable for numbers, specfically: 30 | 31 | * Trigonometry[T] 32 | * Rootable[T] 33 | 34 | An other types useful in implementing scene graphs/engines. 35 | 36 | ## OpenGL I/O 37 | A library that helps get data into/out of graphics memory. This consists of a few core pieces: 38 | 39 | * BufferLoadable[T] - load data into java.nio.ByteBuffers 40 | * VertexArrayObject[T] - load POD-type case classes into VAO. Can load raw vertices, or vertex+index arrays. 41 | 42 | ## OpenGL Shader DSL 43 | A library that allows specifying OpenGL shaders completely in scala. Relies on Linear Algebra types, and the I/O library for sending 44 | data into/out of graphics memory. 45 | 46 | ## Scene Graph 47 | A library to help simplify setting up a Scene in OpenGL and rendering it. This is used for the example application. 48 | 49 | # TODOs 50 | 51 | Here's a set of TODOs for this project. These are just the ones we find interesting enough to list out. There are probably more, if there's one you're interested in working on, list it out and work on it. 52 | 53 | ## Linear Algebra library 54 | - [ ] Add Matrix2x2 55 | - [ ] Add (optimal) Matrix multiplication 56 | - [ ] Create fuzzy equals method (floating point friendly) 57 | - [ ] Optimise matrix inverses 58 | - [ ] Create a legitimate set of tests 59 | - [ ] Derive "Show" typeclass for pretty-printing 60 | - [ ] Matrix +/- operations. 61 | 62 | ## OpenGL I/O library 63 | - [ ] Encode Textures and "opaque" types 64 | - [ ] Write tests for buffer loading 65 | 66 | ## Shader DSL 67 | - [ ] use compiler error messages instead of exceptions. 68 | - [ ] support helper/nested methods 69 | - [ ] support opaque type: Sampler2D 70 | - [ ] support unsigned ints 71 | - [ ] Add check for outside references that are not uniforms 72 | - [ ] Figure out a way to enforce vertex input in GLSL aligns with vertex buffer input from CPU. 73 | 74 | ## Scene Graph 75 | - [ ] Material support 76 | - [ ] non-static scenes? 77 | 78 | ## Example 79 | - [ ] load/use a texture 80 | - [ ] add a post-process shader (anti-aliasing?) 81 | 82 | ## Bugs to be reported to Dotty 83 | 84 | - [ ] Investigate using float literals and open types leading to stack-overflow 85 | - [ ] Investigate having open-type + constrained type in overloaded method causing 86 | issues in type inference/method discovery 87 | - [ ] Investigate anonymous delegate for multiple fully qualified type constructor 88 | having name collisions. 89 | - [ ] Report inconsistency of companion-object vs. free-floating delegates/givens. 90 | 91 | # Assets 92 | 93 | Mesh assets are graciously provided from NASA, with others. 94 | See [https://github.com/nasa/NASA-3D-Resources](https://github.com/nasa/NASA-3D-Resources) for more options. 95 | 96 | # Community Guidelines 97 | 98 | See [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) 99 | 100 | # License 101 | 102 | Copyright 2019 Google LLC 103 | 104 | Licensed under the Apache License, Version 2.0 (the "License"); 105 | you may not use this file except in compliance with the License. 106 | You may obtain a copy of the License at 107 | 108 | https://www.apache.org/licenses/LICENSE-2.0 109 | 110 | Unless required by applicable law or agreed to in writing, software 111 | distributed under the License is distributed on an "AS IS" BASIS, 112 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 113 | See the License for the specific language governing permissions and 114 | limitations under the License. 115 | 116 | See LICENSE.md for more info. -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the 73 | Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to 74 | receive and address reported violations of the code of conduct. They will then 75 | work with a committee consisting of representatives from the Open Source 76 | Programs Office and the Google Open Source Strategy team. If for any reason you 77 | are uncomfortable reaching out the Project Steward, please email 78 | opensource@google.com. 79 | 80 | We will investigate every complaint, but you may not receive a direct response. 81 | We will use our discretion in determining when and how to follow up on reported 82 | incidents, which may range from not taking action to permanent expulsion from 83 | the project and project-sponsored spaces. We will notify the accused of the 84 | report and provide them an opportunity to discuss it before any action is taken. 85 | The identity of the reporter will be omitted from the details of the report 86 | supplied to the accused. In potentially harmful situations, such as ongoing 87 | harassment or threats to anyone's safety, we may take action without notice. 88 | 89 | ## Attribution 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 92 | available at 93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 94 | -------------------------------------------------------------------------------- /shader/src/main/scala/com/jsuereth/gl/shaders/BasicShaderProgram.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.shaders 18 | 19 | import org.lwjgl.opengl.{ 20 | GL11, 21 | GL20 22 | } 23 | import com.jsuereth.gl.io.{ 24 | ShaderUniformLoadable, 25 | ShaderLoadingEnvironment 26 | } 27 | import org.lwjgl.system.MemoryStack 28 | 29 | enum Shader(val id: Int) { 30 | case Vertex extends Shader(GL20.GL_VERTEX_SHADER) 31 | case Fragment extends Shader(GL20.GL_FRAGMENT_SHADER) 32 | } 33 | 34 | class ShaderException(msg: String, source: String, cause: Throwable) extends Exception(s"$msg\n\n$source", cause) { 35 | def this(msg: String, source: String) = this(msg, source: String, null) 36 | } 37 | 38 | /** Compiles a shader and returns the id of the shader. */ 39 | def compileShader(shaderType: Shader, source: String): Int = { 40 | val shader = GL20.glCreateShader(shaderType.id) 41 | try { 42 | if(shader == 0) throw ShaderException("Unable to construct shader!", source) 43 | GL20.glShaderSource(shader, source) 44 | GL20.glCompileShader(shader) 45 | if(GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) { 46 | throw ShaderException(GL20.glGetShaderInfoLog(shader, 2000), source) 47 | } 48 | } catch { 49 | case e: Exception => 50 | GL20.glDeleteShader(shader) 51 | throw e 52 | } 53 | shader 54 | } 55 | /** Creates a shader program and linkes in a set of shaders together. */ 56 | def linkShaders(compiledShaders: Int*): Int = { 57 | val program = GL20.glCreateProgram() 58 | try { 59 | for (shader <- compiledShaders) GL20.glAttachShader(program, shader) 60 | 61 | GL20.glLinkProgram(program) 62 | if (GL20.glGetProgrami(program, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) { 63 | throw ShaderException(GL20.glGetProgramInfoLog(program, 2000), "") 64 | } 65 | // VALIDATION! 66 | GL20.glValidateProgram(program) 67 | if(GL20.glGetProgrami(program, GL20.GL_VALIDATE_STATUS) == GL11.GL_FALSE) { 68 | throw ShaderException(GL20.glGetProgramInfoLog(program, 2000), "") 69 | } 70 | } catch { 71 | // Make sure we clean up if we run into issues. 72 | case t => GL20.glDeleteProgram(program); throw t 73 | } 74 | program 75 | } 76 | /** looks up the "location" to a uniform in a shader program. */ 77 | def lookupUniform(program: Int, name: String): Int = 78 | GL20.glGetUniformLocation(program, name) 79 | 80 | 81 | abstract class BasicShaderProgram { 82 | /** Returns the vertex shader for this program. */ 83 | def vertexShaderCode: String 84 | /** Returns the fragment shader for this program. */ 85 | def fragmentShaderCode: String 86 | 87 | private var programId: Int = 0 88 | /** Loas the configured shader into the graphics card. */ 89 | def load() : Unit = { 90 | unload() 91 | // TODO - remember shader ids for unloading? 92 | programId = linkShaders(compileShader(Shader.Vertex, vertexShaderCode), 93 | compileShader(Shader.Fragment, fragmentShaderCode)) 94 | } 95 | /** Removes this shader from OpenGL/Graphics card memory. */ 96 | def unload(): Unit = if (programId != 0) { 97 | // TODO - unlink shaders? 98 | // GL20.glDetachShader(program, shader) 99 | // GL20.glDeleteShader(shader) 100 | GL20.glDeleteProgram(programId) 101 | programId = 0 102 | } 103 | /** Configures the OpenGL pipeline to use this program. */ 104 | def bind(): Unit = GL20.glUseProgram(programId) 105 | /** Configures OpenGL pipeline to not use any shader. */ 106 | def unbind(): Unit = GL20.glUseProgram(0) 107 | 108 | 109 | /** Our version of uniform which binds to the shader being compiled by this program. */ 110 | protected class MyUniform[T : ShaderUniformLoadable](override val name: String) extends Uniform[T] { 111 | var location: Int = 0 112 | // TODO - does this need to be threadsafe? 113 | def :=(value: T)(using ShaderLoadingEnvironment): Unit = { 114 | if (location == 0) { 115 | location = GL20.glGetUniformLocation(programId, name) 116 | } 117 | summon[ShaderUniformLoadable[T]].loadUniform(location, value) 118 | } 119 | 120 | } 121 | 122 | def makeUniform[T : ShaderUniformLoadable](name: String): Uniform[T] = { 123 | MyUniform[T](name) 124 | } 125 | 126 | // Temporary for debugging purposes only. 127 | def debugUniform(name: String): Int = GL20.glGetUniformLocation(programId, name) 128 | } -------------------------------------------------------------------------------- /scene/src/main/scala/com/jsuereth/gl/mesh/Mesh.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package mesh 19 | 20 | import math.{given _, _} 21 | import org.lwjgl.system.MemoryStack 22 | import io.{VertexArrayObject, BufferLoadable, vaoAttributes} 23 | 24 | /** Represents a single point in a mesh, and all data we can/could send down. */ 25 | case class MeshPoint( 26 | vert: Vec3[Float], 27 | normal: Vec3[Float], 28 | tex: Vec2[Float]) derives BufferLoadable 29 | object MeshPoint 30 | 31 | // TODO - make this an enum 32 | sealed trait Face { 33 | def vertices: Seq[FaceIndex] 34 | } 35 | /** Represents a triangle defined as an index-reference into a Mesh3d. */ 36 | final case class TriangleFace(one: FaceIndex, two: FaceIndex, three: FaceIndex) extends Face { 37 | override def vertices: Seq[FaceIndex] = Seq(one,two,three) 38 | } 39 | final case class QuadFace(one: FaceIndex, two: FaceIndex, three: FaceIndex, four: FaceIndex) extends Face { 40 | override def vertices: Seq[FaceIndex] = Seq(one,two,three,four) 41 | } 42 | /** Index reference into a Mesh3d for a face-definition. */ 43 | final case class FaceIndex(vertix: Int, texture: Int, normal: Int) 44 | 45 | 46 | /** 47 | * A rendderable 3d Mesh. 48 | * - This needs a LOT of cleanup. We should not blindly take in OBJ format and use it, we should clean it before 49 | * creating this data structure. 50 | */ 51 | trait Mesh3d { 52 | /** The verticies on this mesh. */ 53 | def vertices: Seq[Vec3[Float]] 54 | /** The normals. */ 55 | def normals: Seq[Vec3[Float]] 56 | /** Texture coordinates. */ 57 | def textureCoords: Seq[Vec2[Float]] 58 | 59 | /** The faces for this mesh. Generally an index-reference to the other members. */ 60 | def faces: Seq[Face] 61 | /** A flattening of indicies to those of the vertex. */ 62 | private def allIndicies: Seq[FaceIndex] = faces.flatMap(_.vertices).distinct.toSeq 63 | /** Expensive to compute mechanism of lining up shared vertex/normal/texel coordinates. */ 64 | private def points: Seq[MeshPoint] = { 65 | for(faceIdx <- allIndicies) yield { 66 | val vertex = vertices(faceIdx.vertix - 1) 67 | val normal = 68 | if(faceIdx.normal == 0) Vec3(0.0f,0.0f,0.0f) 69 | else normals(faceIdx.normal - 1) 70 | val texture = 71 | if (faceIdx.texture == 0) Vec2(0f,0f) 72 | else textureCoords(faceIdx.texture - 1) 73 | MeshPoint(vertex,normal,texture) 74 | } 75 | } 76 | // TODO - don't keep indicies in such a wierd format... 77 | private def idxes: Seq[Int] = { 78 | val indexMap = allIndicies.zipWithIndex.toMap 79 | for { 80 | face <- faces 81 | v <- face.vertices 82 | idx <- indexMap get v 83 | } yield idx 84 | } 85 | 86 | // TODO - cache/store of which VAOs are loaded and ability to dynamically unload them? 87 | /** Loads this mesh into a VAO that can be rendered. */ 88 | def loadVao(using MemoryStack): VertexArrayObject[MeshPoint] = { 89 | // TODO - Figure out if triangles or quads. 90 | VertexArrayObject.loadWithIndex(points, idxes) 91 | } 92 | } 93 | 94 | object Mesh3d { 95 | private val oneThird = 1.0f / 3.0f 96 | /** Calculate the centroid (assuming equal weight on points). */ 97 | def centroid(mesh: Mesh3d): Vec3[Float] = { 98 | // Algorithm 99 | // C = Centroid , A = (area of a face * 2) 100 | // R = face centroid = average of vertices making the face 101 | // C = [sum of all (A*R)] / [sum of all R] 102 | var sumAreaTimesFaceCentroidX = 0f 103 | var sumAreaTimesFaceCentroidY = 0f 104 | var sumAreaTimesFaceCentroidZ = 9f 105 | var sumArea = 0.0f 106 | for (face <- mesh.faces) { 107 | val TriangleFace(fone, ftwo, fthree) = face 108 | val one = mesh.vertices(fone.vertix-1) 109 | val two = mesh.vertices(ftwo.vertix-1) 110 | val three = mesh.vertices(fthree.vertix-1) 111 | val u = two - one 112 | val v = three - one 113 | val crossed = u cross v 114 | val area = crossed.length.toFloat 115 | sumArea += area 116 | val x = (one.x + two.x + three.x) * oneThird 117 | sumAreaTimesFaceCentroidX += (x * area) 118 | val y = (one.y + two.y + three.y) * oneThird 119 | sumAreaTimesFaceCentroidY += (y * area) 120 | val z = (one.z + two.z + three.z) * oneThird 121 | sumAreaTimesFaceCentroidZ += (z * area) 122 | } 123 | val x = sumAreaTimesFaceCentroidX / sumArea 124 | val y = sumAreaTimesFaceCentroidY / sumArea 125 | val z = sumAreaTimesFaceCentroidZ / sumArea 126 | Vec3(x, y, z) 127 | } 128 | // TODO - Normalize on centroid... 129 | } 130 | 131 | -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/io/VertexArrayObject.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.io 18 | 19 | import org.lwjgl.system.MemoryStack 20 | import org.lwjgl.opengl.GL11.{ 21 | GL_TRIANGLES, 22 | GL_UNSIGNED_INT, 23 | glDrawArrays, 24 | glDrawElements 25 | } 26 | import org.lwjgl.opengl.GL15.{ 27 | GL_ARRAY_BUFFER, 28 | GL_ELEMENT_ARRAY_BUFFER, 29 | GL_STATIC_DRAW, 30 | glGenBuffers, 31 | glBindBuffer, 32 | glBufferData 33 | } 34 | import org.lwjgl.opengl.GL20.{ 35 | glEnableVertexAttribArray 36 | } 37 | import org.lwjgl.opengl.GL30.{ 38 | glGenVertexArrays, 39 | glDeleteVertexArrays, 40 | glBindVertexArray 41 | } 42 | import java.nio.ByteBuffer 43 | 44 | /** 45 | * Represents a vertex array object stored in graphics memory. 46 | * 47 | * The object stores vertex data of type VertexType. The layout 48 | * is determined via the {{vaoAttributes[VertexType]}} method. 49 | */ 50 | trait VertexArrayObject[VertexType] extends GLBuffer { 51 | /** Binds this VAO for usage. */ 52 | def bind(): Unit = glBindVertexArray(id) 53 | /** Removes this VAO from being used. */ 54 | def unbind(): Unit = glBindVertexArray(0) 55 | /** Renders the VAO through a shader pipeline. */ 56 | def draw(): Unit 57 | 58 | // TODO - Clean VBOs? 59 | override def close(): Unit = glDeleteVertexArrays(id) 60 | } 61 | object VertexArrayObject { 62 | /** Loads a VAO whose elements will align with the passed in type. 63 | * @tparam T the plain-old-data type (case class) where each member becomes one of the vertex attribute pointers in this VAO. 64 | * @param vertices A sequence of all the verticies to load into graphics memory. 65 | * @param indicies A set of indicies to refereence in the vertex array when drawing geometry. 66 | * 67 | * Note: This assumes TRIANGLES are being sent for drawing. 68 | */ 69 | inline def loadWithIndex[T : BufferLoadable](vertices: Seq[T], indices: Seq[Int])(using MemoryStack): VertexArrayObject[T] = { 70 | val id = glGenVertexArrays() 71 | glBindVertexArray(id) 72 | val attrs: Array[VaoAttribute] = vaoAttributes[T] 73 | // Create the Vertex data 74 | val vboVertex = glGenBuffers() 75 | glBindBuffer(GL_ARRAY_BUFFER, vboVertex) 76 | withLoadedBuf(vertices)(glBufferData(GL_ARRAY_BUFFER, _, GL_STATIC_DRAW)) 77 | attrs.foreach(_.define()) 78 | // Now create the index buffer. 79 | val vboIndex = glGenBuffers() 80 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndex) 81 | withLoadedBuf(indices)(glBufferData(GL_ELEMENT_ARRAY_BUFFER, _, GL_STATIC_DRAW)) 82 | IndexedVertexArrayObject(id, attrs, indices.size) 83 | } 84 | /** Loads a VAO whose elements will align with the passed in type. 85 | * @tparam T the plain-old-data type (case class) where each member becomes one of the vertex attribute pointers in this VAO. 86 | * @param vertices A sequence of all the verticies to load into graphics memory. 87 | * 88 | * Note: This assumes TRIANGLES are being sent for drawing. 89 | */ 90 | inline def loadRaw[T : BufferLoadable](vertices: Seq[T])(using MemoryStack): VertexArrayObject[T] = { 91 | val id = glGenVertexArrays() 92 | val vertByteSize = sizeOf[T]*vertices.size 93 | val attrs = vaoAttributes[T] 94 | glBindVertexArray(id) 95 | val vboVertex = glGenBuffers() 96 | glBindBuffer(GL_ARRAY_BUFFER, vboVertex) 97 | withLoadedBuf(vertices)(glBufferData(GL_ARRAY_BUFFER, _, GL_STATIC_DRAW)) 98 | attrs.foreach(_.define()) 99 | RawVertexArrayObject(id, attrs, vertices.size) 100 | } 101 | } 102 | /** This class can render a VAO where vertex data is rendered in order. */ 103 | class RawVertexArrayObject[VertexType](override val id: Int, 104 | attrs: Array[VaoAttribute], 105 | vertexCount: Int, 106 | drawMode: Int = GL_TRIANGLES) extends VertexArrayObject[VertexType] { 107 | def draw(): Unit = withBound { 108 | attrs.iterator.map(_.idx).foreach(glEnableVertexAttribArray(_)) 109 | glDrawArrays(drawMode, 0, vertexCount) 110 | } 111 | override def close(): Unit = glDeleteVertexArrays(id) 112 | } 113 | 114 | /** This class can render a VAO where you load vertex data separately from index data. */ 115 | class IndexedVertexArrayObject[VertexType](override val id: Int, 116 | attrs: Array[VaoAttribute], 117 | indexCount: Int, 118 | drawMode: Int = GL_TRIANGLES) extends VertexArrayObject[VertexType] { 119 | def draw(): Unit = withBound { 120 | attrs.iterator.map(_.idx).foreach(glEnableVertexAttribArray(_)) 121 | // TODO openGL index type... 122 | glDrawElements(drawMode, indexCount, GL_UNSIGNED_INT, 0) 123 | } 124 | } -------------------------------------------------------------------------------- /shader/src/main/scala/com/jsuereth/gl/shaders/codegen/transform.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.shaders 18 | package codegen 19 | 20 | def Refs(ast: Ast | Declaration | Statement | Expr): Set[String] = ast match { 21 | case x: Ast => x.decls.iterator.flatMap(Refs).toSet 22 | case Declaration.Method(_, _, block) => block.flatMap(Refs).toSet 23 | case Statement.Effect(expr) => Refs(expr) 24 | case Statement.Assign(_, expr) => Refs(expr) 25 | case Statement.LocalVariable(_, _, Some(expr)) => Refs(expr) 26 | case Expr.MethodCall(name, args) => args.flatMap(Refs).toSet 27 | case Expr.Id(name) => Set(name) 28 | case Expr.Operator(name, lhs, rhs) => Refs(lhs) ++ Refs(rhs) 29 | case Expr.Negate(expr) => Refs(expr) 30 | case Expr.Terenary(cond, lhs, rhs) => Refs(cond) ++ Refs(lhs) ++ Refs(rhs) 31 | case Expr.Select(lhs, rhs) => Refs(lhs) 32 | case _ => Set() 33 | } 34 | 35 | /** An overridable transformer that provides basic "transform everything" impl. */ 36 | class AstTransformer { 37 | def transform(ast: Ast): Ast = Ast(ast.decls.map(transform)) 38 | def transform(decl: Declaration): Declaration = decl match { 39 | case Declaration.Method(name, tpe, block) => Declaration.Method(name, tpe, block.map(transform)) 40 | case _ => decl 41 | } 42 | def transform(stmt: Statement): Statement = stmt match { 43 | case Statement.Effect(expr) => Statement.Effect(transform(expr)) 44 | case Statement.Assign(name, expr) => Statement.Assign(name, transform(expr)) 45 | case Statement.LocalVariable(name, tpe, optExpr) => Statement.LocalVariable(name, tpe, optExpr.map(transform)) 46 | } 47 | def transform(expr: Expr): Expr = expr match { 48 | case Expr.MethodCall(name, args) => Expr.MethodCall(name, args.map(transform)) 49 | case Expr.Id(_) => expr 50 | case Expr.Operator(name, lhs, rhs) => Expr.Operator(name, transform(lhs), transform(rhs)) 51 | case Expr.Negate(expr) => Expr.Negate(transform(expr)) 52 | case Expr.Terenary(cond, lhs, rhs) => Expr.Terenary(transform(cond), transform(lhs), transform(rhs)) 53 | case Expr.Select(lhs, rhs) => Expr.Select(transform(lhs), rhs) 54 | } 55 | } 56 | 57 | object PlaceholderRemover extends AstTransformer { 58 | def isPlaceholder(e: Expr | Statement): Boolean = e match { 59 | case Statement.Effect(e) => isPlaceholder(e) 60 | case Expr.Id("") => true 61 | case _ => false 62 | } 63 | 64 | override def transform(decl: Declaration): Declaration = decl match { 65 | case Declaration.Method(name, tpe, block) => Declaration.Method(name, tpe, block.filterNot(isPlaceholder)) 66 | case _ => decl 67 | } 68 | } 69 | 70 | class OutputVariableTransformer(vars: Set[(String,String)]) extends AstTransformer { 71 | override def transform(ast: Ast): Ast = { 72 | val outs = for((name, tpe) <- vars) yield Declaration.Output(name, tpe, None) 73 | Ast(outs.toSeq ++ ast.decls.map(transform)) 74 | } 75 | override def transform(stmt: Statement): Statement = stmt match { 76 | case Statement.LocalVariable(name, tpe, Some(expr)) if vars(name -> tpe) => Statement.Assign(name, expr) 77 | case Statement.LocalVariable(name, tpe, None) if vars(name -> tpe) => Statement.Effect(Expr.Id("")) // TODO - alternative way to get rid of statements. 78 | case _ => super.transform(stmt) 79 | } 80 | } 81 | 82 | class InputVariableTransformer(vars: Set[(String,String)]) extends AstTransformer { 83 | override def transform(ast: Ast): Ast = { 84 | val ins = for((name, tpe) <- vars) yield Declaration.Input(name, tpe, None) 85 | Ast(ins.toSeq ++ ast.decls.map(transform)) 86 | } 87 | override def transform(stmt: Statement): Statement = stmt match { 88 | case Statement.LocalVariable(name, tpe, Some(expr)) if vars(name -> tpe) => Statement.Assign(name, expr) 89 | case Statement.LocalVariable(name, tpe, None) if vars(name -> tpe) => Statement.Effect(Expr.Id("")) // TODO - alternative way to get rid of statements. 90 | case _ => super.transform(stmt) 91 | } 92 | } 93 | 94 | /** Autocorrects shared reference variables between vertex + pixel to be output/input appropriately. */ 95 | def TransformAst(vertex: Ast, pixel: Ast): (Ast, Ast) = { 96 | // TODO - this is a really horrible implementation, not very safe/good. 97 | val localVariables = 98 | (for { 99 | Declaration.Method(_,_, block) <- vertex.decls.iterator.filter(_.isInstanceOf[Declaration.Method]) 100 | Statement.LocalVariable(name, tpe, _) <- block.iterator.filter(_.isInstanceOf[Statement.LocalVariable]) 101 | } yield name -> tpe).toMap 102 | val sharedVariables = 103 | for { 104 | name <- Refs(pixel) 105 | if localVariables.contains(name) 106 | } yield name -> localVariables(name) 107 | val vTransform = new OutputVariableTransformer(sharedVariables) 108 | val fTransform = new InputVariableTransformer(sharedVariables) 109 | //throw new Exception(s"Found shared references: ${sharedVariables.mkString(", ")}") 110 | (PlaceholderRemover.transform(vTransform.transform(vertex)),PlaceholderRemover.transform(fTransform.transform(pixel))) 111 | } -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/io/BufferLoadable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package io 19 | 20 | 21 | import java.nio.ByteBuffer 22 | import scala.compiletime.{erasedValue,summonFrom} 23 | 24 | /** 25 | * Loads a value into a byte buffer. 26 | * This can be derived for most final case classes (i.e. PODs). 27 | */ 28 | trait BufferLoadable[T] { 29 | /** Loads a value into a byte buffer. */ 30 | def load(value: T, buf: ByteBuffer): Unit 31 | } 32 | object BufferLoadable { 33 | import deriving._ 34 | 35 | /* A quick and dirty mechanism to derive loading product-types into a buffer. */ 36 | inline def derived[A](using m: Mirror.Of[A]): BufferLoadable[A] = new BufferLoadable[A] { 37 | def load(v: A, buf: ByteBuffer): Unit = { 38 | inline m match { 39 | case m: Mirror.ProductOf[A] => writeElems[m.MirroredElemTypes](buf, v, 0) 40 | case _ => compiletime.error("Cannot derive buffer loading for non-product classes") 41 | } 42 | } 43 | } 44 | // peels off a layer of the tuple and writes it into the buffer. 45 | inline def writeElems[Elems <: Tuple](buf: ByteBuffer, v: Any, idx: Int): Unit = 46 | inline erasedValue[Elems] match { 47 | case _: (elem *: elems1) => 48 | loadInl[elem](productElement[elem](v, idx), buf) 49 | writeElems[elems1](buf, v, idx+1) 50 | case _: Unit => 51 | } 52 | // Looks up the implicit a buffer loadable for a given type. 53 | inline def loadInl[A](value: A, buf: ByteBuffer): Unit = 54 | summonFrom { 55 | case loader: BufferLoadable[A] => loader.load(value,buf) 56 | } 57 | 58 | // Defines all the buffer loading for primitives. 59 | // TODO - figure out if we can autogenerate this or clean it up in otherways. 60 | given BufferLoadable[Float] { 61 | def load(value: Float, buf: ByteBuffer): Unit = 62 | buf.putFloat(value) 63 | } 64 | given BufferLoadable[Double] { 65 | def load(value: Double, buf: ByteBuffer): Unit = 66 | buf.putDouble(value) 67 | } 68 | given BufferLoadable[Boolean] { 69 | def load(value: Boolean, buf: ByteBuffer): Unit = 70 | buf.put(if(value) 1.toByte else 0.toByte) 71 | } 72 | given BufferLoadable[Byte] { 73 | def load(value: Byte, buf: ByteBuffer): Unit = 74 | buf.put(value) 75 | } 76 | given BufferLoadable[Short] { 77 | def load(value: Short, buf: ByteBuffer): Unit = 78 | buf.putShort(value) 79 | } 80 | given BufferLoadable[Int] { 81 | def load(value: Int, buf: ByteBuffer): Unit = 82 | buf.putInt(value) 83 | } 84 | given BufferLoadable[Long] { 85 | def load(value: Long, buf: ByteBuffer): Unit = 86 | buf.putLong(value) 87 | } 88 | 89 | given [T](using BufferLoadable[T]) as BufferLoadable[Seq[T]] { 90 | def load(value: Seq[T], buf: ByteBuffer): Unit = { 91 | val i = value.iterator 92 | while(i.hasNext) { 93 | buf.load(i.next) 94 | } 95 | } 96 | } 97 | 98 | import com.jsuereth.gl.math._ 99 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Matrix3x3[T]] { 100 | def load(value: Matrix3x3[T], buf: java.nio.ByteBuffer): Unit = { 101 | buf.loadArray(value.values, 9, 0) 102 | } 103 | } 104 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Matrix4x4[T]] { 105 | def load(value: Matrix4x4[T], buf: java.nio.ByteBuffer): Unit = { 106 | buf.loadArray(value.values, 16, 0) 107 | } 108 | } 109 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Vec2[T]] { 110 | def load(value: Vec2[T], buf: java.nio.ByteBuffer): Unit = { 111 | buf.load(value.x) 112 | buf.load(value.y) 113 | } 114 | } 115 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Vec4[T]] { 116 | def load(value: Vec4[T], buf: java.nio.ByteBuffer): Unit = { 117 | buf.load(value.x) 118 | buf.load(value.y) 119 | buf.load(value.z) 120 | buf.load(value.w) 121 | } 122 | } 123 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Vec3[T]] { 124 | def load(value: Vec3[T], buf: java.nio.ByteBuffer): Unit = { 125 | buf.load(value.x) 126 | buf.load(value.y) 127 | buf.load(value.z) 128 | } 129 | } 130 | } 131 | /** 132 | * Extension method for ByteBuffer. 133 | * Allows calling 'load' on non-primitive types. 134 | */ 135 | def (buf: ByteBuffer) load[T](value: T)(using loader: BufferLoadable[T]): Unit = loader.load(value, buf) 136 | 137 | /** 138 | * Extension method for ByteBuffer. 139 | * Allows calling 'load' for partial array values. 140 | */ 141 | def (buf: ByteBuffer) loadArray[T](value: Array[T], length: Int, offset: Int)(using loader: BufferLoadable[T]): Unit = { 142 | for (i <- offset until (length+offset) if i < value.length) 143 | loader.load(value(i), buf) 144 | } 145 | 146 | import org.lwjgl.system.MemoryStack 147 | /** 148 | * Loads a buffer with the given data, flips it for reading and passes it to the lambda. 149 | * Cleans up memory when done. 150 | */ 151 | inline def withLoadedBuf[T : BufferLoadable, A](value: Seq[T])(f: ByteBuffer => A)(using MemoryStack): A = { 152 | val size = sizeOf[T]*value.length 153 | withByteBuffer(size) { buf => 154 | buf.clear() 155 | buf.load(value) 156 | buf.flip() 157 | f(buf) 158 | } 159 | } -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/Quaternion.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.math 18 | 19 | import scala.reflect.ClassTag 20 | 21 | object Quaternion { 22 | /** Computes 1/2 in a given numeric type. */ 23 | def half[T : Fractional]: T = one / two 24 | 25 | def identity[T : Fractional]: Quaternion[T] = Quaternion(one, zero, zero, zero) 26 | /** Generate a quaternion that represents rotation about a given vector. */ 27 | def rotation[T: Fractional : Trigonometry](angle: T, vector: Vec3[T]): Quaternion[T] = 28 | rotationRad(angleToRadians(angle), vector) 29 | /** Generate a quaternion that represents rotation about a given vector. */ 30 | def rotationRad[T : Fractional : Trigonometry](angle: T, vector: Vec3[T]): Quaternion[T] = { 31 | val halfAngle = angle * half[T] 32 | val sinHalfAngle = sin(halfAngle) 33 | val w = cos(halfAngle) 34 | val x = (sinHalfAngle * vector.x) 35 | val y = (sinHalfAngle * vector.y) 36 | val z = (sinHalfAngle * vector.z) 37 | Quaternion(w,x,y,z) 38 | } 39 | /** Converts a Vec3 into the equivalent quaternion. */ 40 | def fromVector[T : Rootable : Fractional](v: Vec3[T]): Quaternion[T] = { 41 | val vn = v.normalize 42 | Quaternion(zero, vn.x, vn.y, vn.z) 43 | } 44 | 45 | // Convert from angular acceleration into Quaternion. 46 | def fromAngularVector[T : Fractional : Rootable : Trigonometry](w: Vec3[T]): Quaternion[T] = 47 | rotationRad(w.length, w.normalize) 48 | 49 | /** takes euler pitch/yaw/roll and turns it into a quaternion. Angles in degrees. */ 50 | def fromEuler[T : Fractional : Rootable : Trigonometry](pitch: T, yaw: T, roll: T): Quaternion[T] = { 51 | // Basically we create 3 Quaternions, one for pitch, one for yaw, one for roll 52 | // and multiply those together. 53 | // the calculation below does the same, just shorter 54 | val p = angleToRadians(pitch) * half[T] 55 | val y = angleToRadians(yaw) * half[T] 56 | val r = angleToRadians(roll) * half[T] 57 | val sinp = sin(p) 58 | val siny = sin(y) 59 | val sinr = sin(r) 60 | val cosp = cos(p) 61 | val cosy = cos(y) 62 | val cosr = cos(r) 63 | val qx = sinr * cosp * cosy - cosr * sinp * siny 64 | val qy = cosr * sinp * cosy + sinr * cosp * siny 65 | val qz = cosr * cosp * siny - sinr * sinp * cosy 66 | val qw = cosr * cosp * cosy + sinr * sinp * siny 67 | Quaternion(qw,qx,qy,qz).normalize 68 | } 69 | } 70 | /** 71 | * A way of representing numbers that is ideal for preserving rotational precision and generating rotation matrices as needed. 72 | * 73 | * w + x*i + y*j + z*k 74 | * 75 | * Note: While this can be representing with any type, some operations will require various aspects of those types, e.g. 76 | * normalization requries having roots. 77 | */ 78 | case class Quaternion[T](w: T, x: T, y: T, z: T) { 79 | 80 | def +(other: Quaternion[T])(using Numeric[T]): Quaternion[T] = 81 | Quaternion(w+other.w, other.x+x, other.y+y, other.z+z) 82 | 83 | def -(other: Quaternion[T])(using Numeric[T]): Quaternion[T] = 84 | Quaternion(w-other.w, x-other.x, y-other.y, z-other.z) 85 | 86 | def *(scalar: T)(using Numeric[T]): Quaternion[T] = 87 | Quaternion(w*scalar, x*scalar, y*scalar, z*scalar) 88 | 89 | /** Hamilton product. This is similar to multiplying two rotation matrices together. */ 90 | def *(other:Quaternion[T])(using Numeric[T]): Quaternion[T] = { 91 | val newW = 92 | w*other.w - x*other.x - y*other.y - z*other.z 93 | val newX = 94 | w*other.x + x*other.w + y*other.z - z*other.y 95 | val newY = 96 | w*other.y - x*other.z + y*other.w + z*other.x 97 | val newZ = 98 | w*other.z + x*other.y - y*other.x + z*other.w 99 | Quaternion(newW, newX, newY, newZ) 100 | } 101 | 102 | // Defined as rotating the vector by this quaternion. 103 | def *(other: Vec3[T])(using ClassTag[T], Fractional[T], Rootable[T]): Vec3[T] = { 104 | val result = 105 | this * Quaternion.fromVector(other) * this.conjugate 106 | Vec3(result.x, result.y, result.z) 107 | } 108 | 109 | def conjugate(using Numeric[T]): Quaternion[T] = 110 | Quaternion(w, -x, -y, -z); 111 | 112 | def normSquared(using Numeric[T]): T = w*w + x*x + y*y + z*z 113 | 114 | def normalize(using Rootable[T], Fractional[T]): Quaternion[T] = { 115 | val ns = normSquared 116 | // Here is a danger mechanism to avoid crazy divisions 117 | // What we really want is a better "near" function for floating point numbers. 118 | //import scala.math.abs 119 | //if(abs(ns) > 0.001f && abs(ns - f.one) > 0.001f) { 120 | val factor = (one / sqrt(ns)) 121 | Quaternion(w * factor, x * factor, y * factor, z * factor) 122 | //} else this 123 | } 124 | 125 | def toMatrix(using ClassTag[T], Rootable[T], Ordering[T], Fractional[T]): Matrix4[T] = normalize.toMatrixFromNorm 126 | 127 | // This did not translate correctly... 128 | private def toMatrixFromNorm(using ClassTag[T], Ordering[T], Fractional[T]): Matrix4x4[T] = { 129 | val Nq = normSquared 130 | val s = if(summon[Ordering[T]].gt(Nq, zero)) two else zero 131 | val X = x*s; val Y = y*s; val Z = z*s 132 | val wX = w*X; val wY = w*Y; val wZ = w*Z 133 | val xX = x*X; val xY = x*Y; val xZ = x*Z 134 | val yY = y*Y; val yZ = y*Z; val zZ = z*Z 135 | 136 | Matrix4x4(Array( 137 | one-(yY+zZ), xY+wZ, xZ-wY, zero, 138 | xY-wZ, one-(xX+zZ), yZ+wX, zero, 139 | xZ+wY, yZ-wX, zero-(xX+yY), zero, 140 | zero, zero, zero, one 141 | )) 142 | } 143 | override def toString: String = s"$w + ${x}i + ${y}j + ${z}j" 144 | } -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/io/VaoAttribute.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package io 19 | 20 | import math._ 21 | 22 | import scala.compiletime.{erasedValue, summonFrom} 23 | import deriving.Mirror 24 | 25 | /** 26 | * Returns the bytes required to store a given value in GLSL. 27 | * Note: This does NOT work on dynamic data. It returns a staticly known size for a type, 28 | * assuming it is packed w/ a BufferLoadable typeclass. 29 | */ 30 | inline def sizeOf[T]: Int = inline erasedValue[T] match { 31 | case _: Boolean => 1 32 | case _: Byte => 1 33 | case _: Short => 2 34 | case _: Int => 4 35 | case _: Long => 8 36 | case _: Float => 4 37 | case _: Double => 8 38 | case _: Vec2[t] => 2*sizeOf[t] 39 | case _: Vec3[t] => 3*sizeOf[t] 40 | case _: Vec4[t] => 4*sizeOf[t] 41 | case _: Matrix4x4[t] => 16*sizeOf[t] 42 | case _: Matrix3x3[t] => 9*sizeOf[t] 43 | // TODO - SizedArray opaque type... 44 | case _: (a *: b) => sizeOf[a]+sizeOf[b] 45 | case _: Product => summonFrom { 46 | case given m: Mirror.ProductOf[T] => sizeOf[m.MirroredElemTypes] 47 | case _ => compiletime.error("Product is not plain-old-data, cannot compute the type.") 48 | } 49 | // Note: Unit shows up when we decompose tuples with *: 50 | case _: Unit => 0 51 | case _ => compiletime.error("Cannot discover the binary size of type T.") 52 | } 53 | 54 | import org.lwjgl.opengl.GL11.{ 55 | GL_FLOAT, 56 | GL_INT, 57 | GL_UNSIGNED_INT, // TODO 58 | GL_SHORT, 59 | GL_UNSIGNED_SHORT, // TODO 60 | GL_BYTE, 61 | GL_UNSIGNED_BYTE, // TODO 62 | GL_DOUBLE 63 | } 64 | /** 65 | * Calculates the OpenGL enum for a type we'll pass into a VAO. 66 | * Note: For types like vec2 or mat4x4, this will return the primitive type, 67 | * as OpenGL specifies these with primitive + size. 68 | */ 69 | inline def glType[T]: Int = inline erasedValue[T] match { 70 | // TODO - Modern OpenGL types: GL_FIXED, GL_HALF_FLOAT,GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV 71 | case _: Boolean => GL_BYTE 72 | case _: Byte => GL_BYTE 73 | case _: Short => GL_SHORT 74 | case _: Int => GL_INT 75 | case _: Float => GL_FLOAT 76 | case _: Double => GL_DOUBLE 77 | case _: Vec2[t] => glType[t] 78 | case _: Vec3[t] => glType[t] 79 | case _: Vec4[t] => glType[t] 80 | case _: Matrix4x4[t] => glType[t] 81 | case _: Matrix3x3[t] => glType[t] 82 | case _ => compiletime.error("This type is not a supported OpenGL type") 83 | } 84 | 85 | /** 86 | * Calculates the size for OpenGL vertex attribute calls. 87 | * Essentially just how many of a give primitive are stored in a type. 88 | */ 89 | inline def glSize[T]: Int = inline erasedValue[T] match { 90 | case _: Vec2[t] => 2 91 | case _: Vec3[t] => 3 92 | case _: Vec4[t] => 4 93 | case _: Matrix3x3[t] => 9 94 | case _: Matrix4x4[t] => 16 95 | case _: Boolean => 1 96 | case _: Byte => 1 97 | case _: Short => 1 98 | case _: Int => 1 99 | case _: Float => 1 100 | case _: Double => 1 101 | case _ => compiletime.error("This type is not a supported OpenGL type") 102 | } 103 | 104 | /** Grab the VAO attributes for a member of a case class. */ 105 | inline def attr[T](idx: Int, stride: Int, offset: Int): VaoAttribute = 106 | VaoAttribute(idx, glSize[T], glType[T], stride, offset) 107 | 108 | /** Defines the vertex attribute pointer configuration we need for a VAO. */ 109 | case class VaoAttribute(idx: Int, size: Int, tpe: Int, stride: Int, offset: Int) { 110 | import org.lwjgl.opengl.GL20.{glVertexAttribPointer,glEnableVertexAttribArray} 111 | /** Sends open-gl the VAO attribute pointer information given the index of this attribute. */ 112 | def define(): Unit = 113 | glVertexAttribPointer(idx, size, tpe, false, stride, offset) 114 | def enable(): Unit = 115 | glEnableVertexAttribArray(idx) 116 | 117 | override def toString: String = { 118 | val tpeString = tpe match { 119 | case GL_FLOAT => "float" 120 | case _ => "unknown" 121 | } 122 | s"$idx(type: $tpeString size: $size, stride: $stride, offset: $offset)" 123 | } 124 | } 125 | 126 | /** 127 | * Attempts to construct an array of VAO attribute definitions for a loaded VAO. 128 | * 129 | * - Calculates stride size via total byte-size of T 130 | * - Caclulates offsets via incrementally adding each member of T 131 | * - Automatically determines GL types of members using glType[m] 132 | * - Automatically determines GL size information using glSize[m] 133 | */ 134 | inline def vaoAttributes[T]: Array[VaoAttribute] = { 135 | val stride = sizeOf[T] 136 | inline erasedValue[T] match { 137 | case _: Tuple1[a] => Array(attr[a](0,stride, 0)) 138 | // TODO - use a continuation approach to tease apart tuples, like we do in sizeOf. 139 | case _: (a, b) => Array(attr[a](0, stride, 0), 140 | attr[b](1, stride, sizeOf[a])) 141 | case _: (a, b, c) => Array(attr[a](0, stride, 0), 142 | attr[b](1, stride, sizeOf[a]), 143 | attr[c](2, stride, sizeOf[a]+sizeOf[b])) 144 | case _: (a, b, c, d) => Array(attr[a](0, stride, 0), 145 | attr[b](1, stride, sizeOf[a]), 146 | attr[c](2, stride, sizeOf[a]+sizeOf[b]), 147 | attr[d](3, stride, sizeOf[a]+sizeOf[b]+sizeOf[c])) 148 | case _: Product => 149 | summonFrom { 150 | case given m: Mirror.ProductOf[T] => vaoAttributes[m.MirroredElemTypes] 151 | } 152 | case _ => compiletime.error("Cannot compute the VAO attributes of this type.") 153 | } 154 | } -------------------------------------------------------------------------------- /types.md: -------------------------------------------------------------------------------- 1 | # Mappings from OpenGL GLSL types to Scala types. 2 | 3 | Taken from [OpenGL ES GLSL 3.00 Spec (29 Jan 2016)](https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf) 4 | 5 | 6 | ## Basic types 7 | 8 | | GLSL | Scala | Meaning | 9 | |-------|----------------------------------|---------| 10 | |void |scala.Unit |for functions that do not return a value| 11 | |bool |scala.Boolean |a conditional type, taking on values of true or false| 12 | |int |scala.Int |a signed integer| 13 | |uint |TBD |an unsigned integer| 14 | |float |scala.Float |a single floating-point scalar| 15 | |vec2 |com.jsuereth.gl.math.Vec2[Float] |a two-component floating-point vector| 16 | |vec3 |com.jsuereth.gl.math.Vec3[Float] |a three-component floating-point vector| 17 | |vec4 |com.jsuereth.gl.math.Vec4[Float] |a four-component floating-point vector| 18 | |bvec2 |com.jsuereth.gl.math.Vec2[Boolean]|a two-component Boolean vector| 19 | |bvec3 |com.jsuereth.gl.math.Vec3[Boolean]|a three-component Boolean vector| 20 | |bvec4 |com.jsuereth.gl.math.Vec4[Boolean]|a four-component Boolean vector| 21 | |ivec2 |com.jsuereth.gl.math.Vec2[Int] |a two-component signed integer vector| 22 | |ivec3 |com.jsuereth.gl.math.Vec3[Int] | a three-component signed integer vector| 23 | |ivec4 |com.jsuereth.gl.math.Vec4[Int] |a four-component signed integer vector| 24 | |uvec2 |com.jsuereth.gl.math.Vec2[Uint] |a two-component unsigned integer vector| 25 | |uvec3 |com.jsuereth.gl.math.Vec3[Uint] |a three-component unsigned integer vector| 26 | |uvec4 |com.jsuereth.gl.math.Vec4[UInt] |a four-component unsigned integer vector| 27 | |mat2 |TBD |a 2×2 floating-point matrix| 28 | |mat3 |TBD |a 3×3 floating-point matrix| 29 | |mat4 |com.jsuereth.gl.math.Matrix4[Float]|a 4×4 floating-point matrix| 30 | |mat2x2 |TBD|same as a mat2 31 | |mat2x3 |TBD| a floating-point matrix with 2 columns and 3 rows 32 | |mat2x4 |TBD| a floating-point matrix with 2 columns and 4 rows 33 | |mat3x2 |TBD| a floating-point matrix with 3 columns and 2 rows 34 | |mat3x3 |TBD| same as a mat3 35 | |mat3x4 |TBD| a floating-point matrix with 3 columns and 4 rows 36 | |mat4x2 |TBD| a floating-point matrix with 4 columns and 2 rows 37 | |mat4x3 |TBD| a floating-point matrix with 4 columns and 3 rows 38 | |mat4x4 |com.jsuereth.gl.math.Matrix4x4[Float]| same as a mat4 39 | 40 | ## Sampler opaque types 41 | 42 | | GLSL | Scala | Meaning | 43 | |----------------------|--------------------------|---------| 44 | |sampler2D |TBD| a handle for accessing a 2D texture 45 | |sampler3D |TBD| a handle for accessing a 3D texture 46 | |samplerCube |TBD| a handle for accessing a cube mapped texture 47 | |samplerCubeShadow |TBD| a handle for accessing a cube map depth texture with comparison 48 | |sampler2DArray |TBD| a handle for accessing an 2D array texture 49 | |sampler2DArrayShadow |TBD| a handle for accessing an 2D array texture 50 | |isampler2D |TBD| a handle for accessing a signed integer 2D texture 51 | |isampler3D |TBD| a handle for accessing a signed integer 3D texture 52 | |isamplerCube |TBD| a handle for accessing a signed integer 2D cube mapped texture 53 | |isampler2DArray |TBD| a handle for accessing a signed integer 2D array texture 54 | |usampler2D |TBD| a handle for accessing an unsigned integer 2D texture 55 | |usampler3D |TBD| a handle for accessing an unsigned integer 3D texture 56 | |usamplerCube |TBD| a handle for accessing an unsigned integer 2D cube mapped texture 57 | |usampler2DArray |TBD| a handle for accessing an unsigned integer 2D array texture 58 | 59 | 60 | ## Built in operators 61 | 62 | | GLSL | Scala | 63 | |-------|--------| 64 | |x + y | x + y | 65 | |x - y | x - y | 66 | |x * y | x * y | 67 | |x / y | x / y | 68 | |x % y |TBD | 69 | |x++ |TBD | 70 | |x-- |TBD | 71 | |-x | -x | 72 | |~x |TBD | 73 | |!x |TBD | 74 | |x << y |TBD | 75 | |x >> y |TBD | 76 | |x < y |x < y | 77 | |x <= y |x <= y | 78 | |x > y |x > y | 79 | |x >= y |x >= y | 80 | |&&|TBD| 81 | |`||`|TBD| 82 | 83 | ## Built-in trigonometry functions 84 | | GLSL | Scala | 85 | |------|-------| 86 | |radians(degrees)|TBD| 87 | |degress(radians)|TBD| 88 | |sin(angle)|TBD| 89 | |cos(angle)|TBD| 90 | |tan(angle)|TBD| 91 | |asin(x)|TBD| 92 | |acos(x)|TBD| 93 | |atan(x,y)|TBD| 94 | |atan(y_over_x)|TBD| 95 | |sinh(x)|TBD| 96 | |cosh(x)|TBD| 97 | |tanh(x)|TBD| 98 | |asinh(x)|TBD| 99 | |acosh(x)|TBD| 100 | |atanh(x)|TBD| 101 | 102 | # Built-in Exponential Functions 103 | 104 | | GLSL | Scala | 105 | |--------|-------| 106 | |pow(x,y)|java.lang.Math.pow(x,y)| 107 | |exp(x)|TBD| 108 | |log(x)|TBD| 109 | |exp2(x)|TBD| 110 | |log2(x)|TBD| 111 | |sqrt(x)|TBD| 112 | |inversesqrt(x)|TBD| 113 | 114 | ## Built-in Common Functions 115 | 116 | | GLSL | Scala | 117 | |--------|-------| 118 | |abs(x,y)|TBD| 119 | |sign(x)|TBD| 120 | |floor(x)|TBD| 121 | |trunc(x)|TBD| 122 | |round(x)|TBD| 123 | |roundEven(x)|TBD| 124 | |ceil(x)|TBD| 125 | |fract(x)|TBD| 126 | |mod(x,y)|TBD| 127 | |modf(x,y)|TBD| 128 | |min(x,y)|java.lang.Math.min(x,y)| 129 | |max(x,y)|java.lang.Math.max(x,y)| 130 | |clamp(x,minVal, maxVal)|TBD| 131 | |mix(x,y,a)|TBD| 132 | |step(edge,x)|TBD| 133 | |smoothstep(edge1,edge2,x)|TBD| 134 | |isnan(x)|TBD| 135 | |isinf(x)|TBD| 136 | |floatBitsToInt(x)|TBD| 137 | |floatBitsToUint(x)|TBD| 138 | |intBitsToFloat(x)|TBD| 139 | |uintBitsToFloat(x)|TBD| 140 | 141 | 142 | ## TODO - floating point packing ops 143 | 144 | 145 | ## Built-in Geometric Functions 146 | 147 | | GLSL | Scala | 148 | |---------------|----------| 149 | |length(x) |x.length | 150 | |distance(p0,p1)|TBD | 151 | |dot(x,y) |x.dot(y) | 152 | |cross(x,y) |x.cross(y)| 153 | |normalize(x) |x.normalize| 154 | |faceforward(N,I,Nref)|TBD| 155 | |reflect(I,N)|TBD| 156 | |refract(I,N,eta)|TBD| 157 | 158 | 159 | ## Built-in Matrix Functions 160 | | GLSL | Scala | 161 | |---------------|----------| 162 | |matrixCompMulti(x,y)|TBD| 163 | |outerProduct(x,y)|TBD| 164 | |transpose(x)|TBD| 165 | |determinant(x)|x.determinant| 166 | |inverse(x)|x.inverse| 167 | 168 | ## TODO translations for section 8.7 and below 169 | -------------------------------------------------------------------------------- /scene/src/main/scala/com/jsuereth/gl/mesh/parser/ObjParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package mesh 19 | package parser 20 | 21 | 22 | import java.io.{File, InputStream} 23 | import math.{Vec2,Vec3, given _} 24 | 25 | /** The results of parsing an obj file. */ 26 | class ObjParsedMesh(override val vertices: Seq[Vec3[Float]], 27 | override val normals: Seq[Vec3[Float]], 28 | override val textureCoords: Seq[Vec2[Float]], 29 | override val faces: Seq[Face]) extends Mesh3d { 30 | override def toString: String = s"ObjFileMesh(faces=${faces.size})" 31 | } 32 | object ObjFileParser { 33 | def parse(in: File): Map[String,Mesh3d] = 34 | parse(java.io.FileInputStream(in)) 35 | def parse(in: InputStream): Map[String,Mesh3d] = { 36 | val tmp = ObjFileParser() 37 | tmp.parse(in) 38 | } 39 | } 40 | /** A parser for .obj files. Only really handles the ones I've used, not robust. 41 | * Also, ignores material files. 42 | */ 43 | class ObjFileParser { 44 | private var currentObjectParser: Option[(String, ObjMeshParser)] = None 45 | private var meshes = collection.mutable.Map[String, ObjParsedMesh]() 46 | 47 | def parse(in: File): Map[String,ObjParsedMesh] = { 48 | parse(java.io.FileInputStream(in)) 49 | } 50 | 51 | def parse(in: InputStream): Map[String,ObjParsedMesh] = { 52 | try readStream(in) 53 | finally in.close() 54 | meshes.toMap 55 | } 56 | 57 | private def readStream(in: java.io.InputStream): Unit = { 58 | val buffer = java.io.BufferedReader(java.io.InputStreamReader(in)) 59 | def read(): Unit = 60 | buffer.readLine match { 61 | case null => () 62 | case line => 63 | readLine(line) 64 | read() 65 | } 66 | read() 67 | cleanSubParser() 68 | } 69 | 70 | private def cleanSubParser(): Unit = 71 | currentObjectParser match { 72 | case Some((name, parser)) => meshes.put(name, parser.obj) 73 | case _ => None 74 | } 75 | 76 | private def readLine(line: String): Unit = 77 | line match { 78 | case ObjLine("#", _) | "" => // Ignore comments. 79 | case ObjLine("usemtl", mtl +: Nil) => System.err.println("Ignoring material: " + mtl) // TODO - figure out what to do w/ materials. 80 | case ObjLine("o", name +: Nil) => 81 | cleanSubParser() 82 | currentObjectParser = Some((name, ObjMeshParser())) 83 | case msg => 84 | currentObjectParser match { 85 | case Some((_, parser)) => parser.readLine(msg) 86 | case None => 87 | // Handle unnamed objects 88 | currentObjectParser = Some(("unnamed", ObjMeshParser())) 89 | currentObjectParser.get._2.readLine(msg) 90 | } 91 | } 92 | } 93 | 94 | /** stateful parser we use to look for objects within a .obj file. */ 95 | class ObjMeshParser() { 96 | private var vertices = collection.mutable.ArrayBuffer.empty[Vec3[Float]] 97 | private var normals = collection.mutable.ArrayBuffer.empty[Vec3[Float]] 98 | private var textureCoords = collection.mutable.ArrayBuffer.empty[Vec2[Float]] 99 | private var faces = collection.mutable.ArrayBuffer.empty[Face] 100 | /** Read a line of input for a named object. */ 101 | def readLine(line: String): Unit = 102 | line match { 103 | case ObjLine("g", group +: Nil) => System.err.println(s"Ignoring group: $group") 104 | case ObjLine("s", config +: Nil) => System.err.println(s"Ignoring smooth shading config: $config") 105 | case ObjLine("usemtl", mtl +: Nil) => System.err.println("Ignoring material: " + mtl) 106 | case ObjLine("v", P3f(x,y,z)) => vertices.append(Vec3(x, y, z)) // verticies 107 | case ObjLine("vt", P2f(u,v)) => textureCoords.append(Vec2(u,v)) // texture coords 108 | case ObjLine("vt", P3f(u,v,_)) => textureCoords.append(Vec2(u,v)) // ignore 3d texture coords 109 | case ObjLine("vn", P3f(x,y,z)) => normals.append(Vec3(x,y,z).normalize) // vertex normals 110 | case ObjLine("f", F(face)) => faces.append(face) 111 | // TODO - support polylines ("l") 112 | case msg => System.err.println(s"Could not read line in object: $msg") // TODO - better errors. 113 | } 114 | 115 | def obj: ObjParsedMesh = { 116 | // TODO - Clean up imported objects. 117 | // Convert all quad faces into triangles 118 | val fixedFaces: Seq[TriangleFace] = faces.flatMap { 119 | case x: TriangleFace => Seq(x) 120 | case QuadFace(one,two,three,four) => Seq(TriangleFace(one,two,three), TriangleFace(three, four, one)) 121 | }.toSeq 122 | // Generate normals if needed, update faces. 123 | val faces2 = if (normals.isEmpty) generateNormals(fixedFaces) else fixedFaces 124 | ObjParsedMesh(vertices.toSeq, normals.toSeq, textureCoords.toSeq, faces2.toSeq) 125 | } 126 | 127 | private def generateNormals(faces: Seq[TriangleFace]): Seq[TriangleFace] = { 128 | for (v <- vertices) normals += Vec3(0f,0f,0f) 129 | for (TriangleFace(one,two,three) <- faces) { 130 | val A = vertices(one.vertix-1) 131 | val B = vertices(two.vertix-1) 132 | val C = vertices(three.vertix-1) 133 | val p = (B-A).cross(C-A) 134 | normals(one.vertix-1) += p 135 | normals(two.vertix-1) += p 136 | normals(three.vertix-1) += p 137 | } 138 | for (i <- 0 until normals.size) { 139 | normals(i) = normals(i).normalize 140 | } 141 | for { 142 | TriangleFace(one,two,three) <- faces 143 | } yield TriangleFace(one.copy(normal=one.vertix), two.copy(normal=two.vertix), three.copy(normal=three.vertix)) 144 | } 145 | } 146 | 147 | private[mesh] object F { 148 | def unapply(face: Seq[String]): Option[Face] = 149 | face match { 150 | case Seq(FI(one), FI(two), FI(three)) => Some(TriangleFace(one,two,three)) 151 | case Seq(FI(one), FI(two), FI(three), FI(four)) => Some(QuadFace(one,two,three,four)) 152 | case _ => None 153 | } 154 | } 155 | 156 | private[mesh] object FI { 157 | def unapply(face: String): Option[FaceIndex] = 158 | face split "/" match { 159 | case Array(It(one)) => Some(FaceIndex(one, 0, 0)) 160 | case Array(It(one), It(two)) => Some(FaceIndex(one, two, 0)) 161 | case Array(It(one), It(two), It(three)) => Some(FaceIndex(one, two, three)) 162 | case Array(It(one), "", It(three)) => Some(FaceIndex(one, 0, three)) 163 | case _ => None 164 | } 165 | } 166 | 167 | private[parser] object P3f { 168 | def unapply(in: Seq[String]): Option[(Float, Float, Float)] = 169 | in match { 170 | case Seq(Fl(x), Fl(y), Fl(z)) => Some((x,y,z)) 171 | case _ => None 172 | } 173 | } 174 | 175 | private[parser] object P2f { 176 | def unapply(in: Seq[String]): Option[(Float, Float)] = 177 | in match { 178 | case Seq(Fl(x), Fl(y)) => Some(x -> y) 179 | case _ => None 180 | } 181 | } 182 | private[parser] object ObjLine { 183 | def unapply(in: String): Option[(String, Seq[String])] = { 184 | val split = in split "\\s+" 185 | if(split.isEmpty) None 186 | else { 187 | Some(split.head -> split.tail) 188 | } 189 | } 190 | 191 | } 192 | 193 | private[parser] object It { 194 | def unapply(in: String): Option[Int] = 195 | try Some(in.toInt) 196 | catch { 197 | case _: NumberFormatException => None 198 | } 199 | } 200 | private[parser] object Fl { 201 | def unapply(in: String): Option[Float] = 202 | try Some(in.toFloat) 203 | catch { 204 | case _: NumberFormatException => None 205 | } 206 | } -------------------------------------------------------------------------------- /io/src/main/scala/com/jsuereth/gl/io/ShaderUniformLoadable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.io 18 | 19 | 20 | import org.lwjgl.system.MemoryStack 21 | import scala.compiletime.summonFrom 22 | 23 | /** Denotes that a variable can be loaded into an OpenGL shader as a uniform. */ 24 | trait ShaderUniformLoadable[T] { 25 | def loadUniform(location: Int, value: T)(using ShaderLoadingEnvironment): Unit 26 | } 27 | 28 | /** The environment we make use of when loading a shader. */ 29 | trait ShaderLoadingEnvironment { 30 | /** Memory we can use for this shader. */ 31 | def stack: MemoryStack 32 | /** A mechanism we can that lets us dynamically select an available texture slot. */ 33 | def textures: ActiveTextures 34 | 35 | def push(): Unit = textures.push() 36 | def pop(): Unit = textures.pop() 37 | } 38 | 39 | import org.lwjgl.opengl.GL20.{glUniform1f, glUniform1i} 40 | object ShaderUniformLoadable { 41 | /** Helper to pull the stack off the shader loading environment. */ 42 | def stack(using ShaderLoadingEnvironment): MemoryStack = summon[ShaderLoadingEnvironment].stack 43 | /** Helper to grab active texutres off the shader loading environment. */ 44 | def textures(using ShaderLoadingEnvironment): ActiveTextures = summon[ShaderLoadingEnvironment].textures 45 | 46 | 47 | given ShaderUniformLoadable[Float] { 48 | def loadUniform(location: Int, value: Float)(using ShaderLoadingEnvironment): Unit = 49 | glUniform1f(location, value) 50 | } 51 | 52 | given ShaderUniformLoadable[Int] { 53 | def loadUniform(location: Int, value: Int)(using ShaderLoadingEnvironment): Unit = 54 | glUniform1i(location, value) 55 | } 56 | import com.jsuereth.gl.math._ 57 | given Matrix3x3FloatLoader as ShaderUniformLoadable[Matrix3x3[Float]] { 58 | import org.lwjgl.opengl.GL20.glUniformMatrix3fv 59 | def loadUniform(location: Int, value: Matrix3x3[Float])(using ShaderLoadingEnvironment): Unit = { 60 | val buf: java.nio.FloatBuffer = { 61 | // Here we're allocating a stack-buffer. We need to ensure we're in a context that has a GL-transition stack. 62 | val buf = stack.callocFloat(9) 63 | buf.clear() 64 | buf.put(value.values) 65 | buf.flip() 66 | buf 67 | } 68 | glUniformMatrix3fv(location, false, buf) 69 | } 70 | } 71 | given Matrix4x4FloatUniformLoader as ShaderUniformLoadable[Matrix4x4[Float]] { 72 | import org.lwjgl.system.MemoryStack 73 | import org.lwjgl.opengl.GL20.glUniformMatrix4fv 74 | def loadUniform(location: Int, value: Matrix4x4[Float])(using ShaderLoadingEnvironment): Unit = { 75 | val buf: java.nio.FloatBuffer = { 76 | // Here we're allocating a stack-buffer. We need to ensure we're in a context that has a GL-transition stack. 77 | val buf = stack.calloc(sizeOf[Matrix4x4[Float]]) 78 | buf.clear() 79 | buf.load(value) 80 | buf.flip() 81 | buf.asFloatBuffer 82 | } 83 | glUniformMatrix4fv(location, false, buf) 84 | } 85 | } 86 | 87 | given loadVec2Float as ShaderUniformLoadable[Vec2[Float]] { 88 | import org.lwjgl.opengl.GL20.glUniform2f 89 | import org.lwjgl.system.MemoryStack 90 | def loadUniform(location: Int, value: Vec2[Float])(using ShaderLoadingEnvironment): Unit = 91 | glUniform2f(location, value.x, value.y) 92 | } 93 | 94 | given loadVec2Int as ShaderUniformLoadable[Vec2[Int]] { 95 | import org.lwjgl.opengl.GL20.glUniform2i 96 | import org.lwjgl.system.MemoryStack 97 | def loadUniform(location: Int, value: Vec2[Int])(using ShaderLoadingEnvironment): Unit = 98 | glUniform2i(location, value.x, value.y) 99 | } 100 | 101 | given loadVec3Float as ShaderUniformLoadable[Vec3[Float]] { 102 | import org.lwjgl.opengl.GL20.glUniform3f 103 | import org.lwjgl.opengl.GL20.glUniform4f 104 | import org.lwjgl.system.MemoryStack 105 | def loadUniform(location: Int, value: Vec3[Float])(using ShaderLoadingEnvironment): Unit = 106 | glUniform3f(location, value.x, value.y, value.z) 107 | } 108 | 109 | given loadVec3Int as ShaderUniformLoadable[Vec3[Int]] { 110 | import org.lwjgl.opengl.GL20.glUniform3i 111 | import org.lwjgl.system.MemoryStack 112 | def loadUniform(location: Int, value: Vec3[Int])(using ShaderLoadingEnvironment): Unit = 113 | glUniform3i(location, value.x, value.y, value.z) 114 | } 115 | 116 | given loadVec4Float as ShaderUniformLoadable[Vec4[Float]] { 117 | import org.lwjgl.opengl.GL20.glUniform4f 118 | import org.lwjgl.system.MemoryStack 119 | def loadUniform(location: Int, value: Vec4[Float])(using ShaderLoadingEnvironment): Unit = 120 | glUniform4f(location, value.x, value.y, value.z, value.w) 121 | } 122 | 123 | given loadVec4Int as ShaderUniformLoadable[Vec4[Int]] { 124 | import org.lwjgl.opengl.GL20.glUniform4i 125 | import org.lwjgl.system.MemoryStack 126 | def loadUniform(location: Int, value: Vec4[Int])(using ShaderLoadingEnvironment): Unit = 127 | glUniform4i(location, value.x, value.y, value.z, value.w) 128 | } 129 | 130 | // Now opaque types, i.e. textures + buffers. 131 | import com.jsuereth.gl.texture._ 132 | given ShaderUniformLoadable[Texture2D] { 133 | def loadUniform(location: Int, value: Texture2D)(using ShaderLoadingEnvironment): Unit = { 134 | // TODO - figure out how to free acquired textures in a nice way 135 | val id = textures.acquire() 136 | // TOOD - is this needed? 137 | // glEnable(GL_TEXTURE_2D) 138 | activate(id) 139 | value.bind() 140 | // Bind the texture 141 | // Tell OpenGL which id we used. 142 | loadTextureUniform(location, id) 143 | } 144 | } 145 | import deriving._ 146 | inline def uniformSize[T]: Int = 147 | inline compiletime.erasedValue[T] match { 148 | case _: (head *: tail) => uniformSize[head] + uniformSize[tail] 149 | case _: Unit => 0 150 | case _ => summonFrom { 151 | case m: Mirror.ProductOf[T] => uniformSize[m.MirroredElemTypes] 152 | case primitive: ShaderUniformLoadable[T] => 1 153 | case _ => compiletime.error("Type is not a valid uniform value!") 154 | } 155 | } 156 | 157 | /** Derives uniform loading for struct-like case classes. These MUST have statically known sized. */ 158 | inline def derived[T](using m: Mirror.ProductOf[T]): ShaderUniformLoadable[T] = 159 | new ShaderUniformLoadable[T] { 160 | def loadUniform(location: Int, value: T)(using ShaderLoadingEnvironment): Unit = 161 | loadStructAtIdx[m.MirroredElemTypes](location, 0, value.asInstanceOf[Product]) 162 | } 163 | 164 | /** Inline helper to load a value into a location, performing the implicit lookup INLINE. */ 165 | inline def loadOne[T](location: Int, value: T)(using ShaderLoadingEnvironment): Unit = 166 | summonFrom { 167 | case loader: ShaderUniformLoadable[T] => 168 | loader.loadUniform(location, value) 169 | case _ => 170 | compiletime.error("Could not find a uniform serializer for all types!") 171 | } 172 | 173 | /** Peels off a level of case-class property and writes it to a uniform, before continuing to call itself on the next uniform. */ 174 | inline def loadStructAtIdx[RemainingElems](location: Int, idx: Int, value: Product)(using ShaderLoadingEnvironment): Unit = { 175 | inline compiletime.erasedValue[RemainingElems] match { 176 | case _: Unit => () // Base case, no elements left. 177 | case _: Tuple1[head] => loadOne[head](location, value.productElement(idx).asInstanceOf) 178 | case _: (head *: tail) => 179 | // Peel off and load this element. 180 | loadOne[head](location, value.productElement(idx).asInstanceOf) 181 | // Chain the rest of the elements, allowing for nested structures. 182 | val nextLoc = location + uniformSize[head] 183 | loadStructAtIdx[tail](nextLoc, idx+1, value) 184 | } 185 | } 186 | } 187 | 188 | -------------------------------------------------------------------------------- /example/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import org.lwjgl.Version 18 | import org.lwjgl.glfw.Callbacks._ 19 | import org.lwjgl.opengl._ 20 | import org.lwjgl.glfw._ 21 | import org.lwjgl.glfw.GLFW._ 22 | import org.lwjgl.opengl.GL 23 | import org.lwjgl.glfw.GLFW._ 24 | import org.lwjgl.opengl.GL11._ 25 | import org.lwjgl.system.MemoryUtil._ 26 | import com.jsuereth.gl.math.{given _, _} 27 | import com.jsuereth.gl.io.{ 28 | withMemoryStack, 29 | VertexArrayObject, 30 | ShaderLoadingEnvironment, 31 | ActiveTextures 32 | } 33 | import org.lwjgl.system.MemoryStack 34 | import com.jsuereth.gl.mesh.parser.ObjFileParser 35 | import com.jsuereth.gl.scene._ 36 | import com.jsuereth.gl.texture.{ 37 | Texture, 38 | Texture2D 39 | } 40 | import com.jsuereth.gl.math._ 41 | 42 | object Main { 43 | val NULL=0L 44 | val WIDTH = 1920 45 | val HEIGHT = 1080 46 | var window: Long = 0 47 | var scene: Scene = null 48 | 49 | 50 | def run(): Unit = { 51 | System.out.println("Hello LWJGL " + Version.getVersion() + "!"); 52 | System.out.println("Example shaders") 53 | System.out.println("--- Vertex Shader ---") 54 | System.out.println(CartoonShader.vertexShaderCode) 55 | System.out.println("--- Fragment Shader ---") 56 | System.out.println(CartoonShader.fragmentShaderCode) 57 | System.out.println("--- ---") 58 | try { 59 | init() 60 | loop() 61 | 62 | // Release window and window callbacks 63 | glfwFreeCallbacks(window) 64 | glfwDestroyWindow(window) 65 | } finally { 66 | // Terminate GLFW and release the GLFWerrorfun 67 | glfwTerminate() 68 | glfwSetErrorCallback(null).free() 69 | } 70 | } 71 | 72 | private def init(): Unit = { 73 | // Setup an error callback. The default implementation 74 | // will print the error message in System.err. 75 | GLFWErrorCallback.createPrint(System.err).set() 76 | 77 | // Initialize GLFW. Most GLFW functions will not work before doing this. 78 | if (!glfwInit()) { 79 | throw new IllegalStateException("Unable to initialize GLFW"); 80 | } 81 | 82 | // Configure our window 83 | glfwDefaultWindowHints() // optional, the current window hints are already the default 84 | glfwWindowHint(GLFW_VISIBLE, GL_FALSE) // the window will stay hidden after creation 85 | glfwWindowHint(GLFW_RESIZABLE, GL_TRUE) // the window will be resizable 86 | 87 | 88 | 89 | // Create the window 90 | window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World!", NULL, NULL) 91 | if (window == NULL) { 92 | throw new RuntimeException("Failed to create the GLFW window") 93 | } 94 | val cameraAmount = 0.5f 95 | // Setup a key callback. It will be called every time a key is pressed, repeated or released. 96 | glfwSetKeyCallback(window, (window, key, scancode, action, mods) => { 97 | if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { 98 | glfwSetWindowShouldClose(window, true) // We will detect this in the rendering loop 99 | } else if (key == GLFW_KEY_W && action == GLFW_PRESS) { 100 | scene.camera.moveForward(cameraAmount) 101 | } else if (key == GLFW_KEY_S && action == GLFW_PRESS) { 102 | scene.camera.moveForward(-cameraAmount) 103 | } else if (key == GLFW_KEY_A && action == GLFW_PRESS) { 104 | scene.camera.moveRight(-cameraAmount) 105 | } else if (key == GLFW_KEY_D && action == GLFW_PRESS) { 106 | scene.camera.moveRight(cameraAmount) 107 | } else if (key == GLFW_KEY_Z && action == GLFW_PRESS) { 108 | scene.camera.moveUp(-cameraAmount) 109 | } else if (key == GLFW_KEY_X && action == GLFW_PRESS) { 110 | scene.camera.moveUp(cameraAmount) 111 | } else if (key == GLFW_KEY_UP && action == GLFW_PRESS) { 112 | scene.camera.turnUp(cameraAmount) 113 | } else if (key == GLFW_KEY_DOWN && action == GLFW_PRESS) { 114 | scene.camera.turnUp(-cameraAmount) 115 | } else if (key == GLFW_KEY_LEFT && action == GLFW_PRESS) { 116 | scene.camera.turnRight(-cameraAmount) 117 | } else if (key == GLFW_KEY_RIGHT && action == GLFW_PRESS) { 118 | scene.camera.turnRight(cameraAmount) 119 | } 120 | }); 121 | 122 | // Get the resolution of the primary monitor 123 | val vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()) 124 | // Center our window 125 | glfwSetWindowPos( 126 | window, 127 | (vidmode.width() - WIDTH) / 2, 128 | (vidmode.height() - HEIGHT) / 2 129 | ) 130 | 131 | // Make the OpenGL context current 132 | glfwMakeContextCurrent(window) 133 | // Enable v-sync 134 | glfwSwapInterval(1) 135 | 136 | // Make the window visible 137 | glfwShowWindow(window) 138 | } 139 | 140 | private def loop(): Unit = { 141 | // This line is critical for LWJGL's interoperation with GLFW's 142 | // OpenGL context, or any context that is managed externally. 143 | // LWJGL detects the context that is current in the current thread, 144 | // creates the ContextCapabilities instance and makes the OpenGL 145 | // bindings available for use. 146 | GL.createCapabilities() 147 | 148 | // Set the clear color 149 | glClearColor(0.1f, 0.1f, 0.1f, 0.1f) 150 | glViewport(0,0,WIDTH, HEIGHT) 151 | glEnable(GL_TEXTURE) 152 | 153 | 154 | // Load models and shader. 155 | val models = ObjFileParser.parse(getClass.getClassLoader.getResourceAsStream("mesh/deep_space_1_11.obj")) 156 | System.out.println("Done loading models!") 157 | for ((name, mesh) <- models) { 158 | System.err.println(s" - Loaded model [$name] w/ ${mesh.vertices.size} vertices, ${mesh.normals.size} normals, ${mesh.textureCoords.size} texcoords, ${mesh.faces.size} faces") 159 | } 160 | val mesh = 161 | models.iterator.next._2 162 | val uglyTexture = Texture.loadImage(getClass.getClassLoader.getResourceAsStream("mesh/texture/foil_silver_ramp.png")) 163 | 164 | val scaleFactor = 1f 165 | // TODO - start rendering using the scene... 166 | scene = SimpleStaticSceneBuilder(). 167 | add(mesh).scale(scaleFactor,scaleFactor,scaleFactor).orientation(Quaternion.fromEuler(0f,90f,90f)).done(). 168 | add(mesh).scale(scaleFactor,scaleFactor,scaleFactor).pos(Vec3(5f,2f,4f)).orientation(Quaternion.fromEuler(90f,0f,0f)).done(). 169 | add(mesh).scale(scaleFactor,scaleFactor,scaleFactor).pos(Vec3(-5f,4f,4f)).orientation(Quaternion.fromEuler(0f,0f,90f)).done(). 170 | light(Vec3(20f,100f,-5f)). 171 | done() 172 | val projectionMatrix = 173 | Matrix4.perspective(45f, WIDTH.toFloat/HEIGHT.toFloat, 1f, 200f) 174 | val vao = withMemoryStack(mesh.loadVao) 175 | CartoonShader.load() 176 | 177 | System.out.println("-- Shader struct debug --") 178 | System.out.println(s" world: ${CartoonShader.debugUniform("world")}") 179 | System.out.println(s" world.light: ${CartoonShader.debugUniform("world.light")}") 180 | System.out.println(s" world.eye: ${CartoonShader.debugUniform("world.eye")}") 181 | System.out.println(s" world.view: ${CartoonShader.debugUniform("world.view")}") 182 | System.out.println(s" world.projection: ${CartoonShader.debugUniform("world.projection")}") 183 | 184 | // Render a scene using cartoon shader. 185 | def render(): Unit = { 186 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) // clear the framebuffer 187 | glEnable(GL_DEPTH_TEST) 188 | glEnable(GL_CULL_FACE) 189 | glCullFace(GL_BACK) 190 | glEnable(GL_TEXTURE) 191 | glEnable(GL_TEXTURE_2D) 192 | 193 | CartoonShader.bind() 194 | withMemoryStack { 195 | given env as ShaderLoadingEnvironment { 196 | val stack = summon[MemoryStack] 197 | val textures = ActiveTextures() 198 | } 199 | CartoonShader.world := WorldData(light = scene.lights.next, 200 | eye = scene.camera.eyePosition, 201 | view = scene.camera.viewMatrix, 202 | projection = projectionMatrix) 203 | CartoonShader.materialKdTexture := uglyTexture 204 | for (o <- scene.objectsInRenderOrder) { 205 | env.push() 206 | // TODO - pull material from objects. 207 | CartoonShader.materialShininess := 1.3f 208 | CartoonShader.materialKd := 0.5f 209 | CartoonShader.materialKs := 0.4f 210 | CartoonShader.modelMatrix := o.modelMatrix 211 | // TODO - pull the VAO for the model. 212 | vao.draw() 213 | env.pop() 214 | } 215 | } 216 | } 217 | 218 | 219 | 220 | // Run the rendering loop until the user has attempted to close 221 | // the window or has pressed the ESCAPE key. 222 | while (!glfwWindowShouldClose(window)) { 223 | render() 224 | glfwSwapBuffers(window) 225 | // Poll for window events. The key callback above will only be 226 | // invoked during this call. 227 | glfwPollEvents() 228 | } 229 | } 230 | 231 | def main(args: Array[String]): Unit = { 232 | run() 233 | } 234 | } -------------------------------------------------------------------------------- /math/src/main/scala/com/jsuereth/gl/math/Matrix4.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl 18 | package math 19 | 20 | import scala.reflect.ClassTag 21 | 22 | // Aliases 23 | type Matrix4[T] = Matrix4x4[T] 24 | val Matrix4 = Matrix4x4 25 | 26 | /** A matrix library for floating point values, meant to be relatively efficient and the analog of GLSL matrix. */ 27 | class Matrix4x4[T : ClassTag](val values: Array[T]) { 28 | // row-column formats 29 | def m11: T = values(0) 30 | def m21: T = values(1) 31 | def m31: T = values(2) 32 | def m41: T = values(3) 33 | def m12: T = values(4) 34 | def m22: T = values(5) 35 | def m32: T = values(6) 36 | def m42: T = values(7) 37 | def m13: T = values(8) 38 | def m23: T = values(9) 39 | def m33: T = values(10) 40 | def m43: T = values(11) 41 | def m14: T = values(12) 42 | def m24: T = values(13) 43 | def m34: T = values(14) 44 | def m44: T = values(15) 45 | 46 | 47 | def apply(row: Int)(col: Int): T = 48 | values(row + (col*4)) 49 | 50 | def *(scale: T)(using Numeric[T]): Matrix4x4[T] = 51 | Matrix4x4(values map (_ * scale)) 52 | 53 | def *(point: Vec4[T])(using Numeric[T]): Vec4[T] = { 54 | import point.{x,y,z,w} 55 | val newX = m11*x + m12*y + m13*z + m14*w 56 | val newY = m21*x + m22*y + m23*z + m24*w 57 | val newZ = m31*x + m32*y + m33*z + m34*w 58 | val newW = m41*x + m42*y + m43*z + m44*w 59 | Vec4(newX, newY, newZ, newW) 60 | } 61 | 62 | def *(other: Matrix4x4[T])(using Numeric[T]): Matrix4x4[T] = { 63 | // TODO - attempt to use Coppersmith–Winograd algorithm? 64 | // For now we do this naively, which is possibly more efficeint. 65 | val next = new Array[T](16) 66 | def idx(row: Int, col: Int): Int = row + (col*4) 67 | // TODO - Should we unroll this? 68 | for { 69 | i <- 0 until 4 70 | j <- 0 until 4 71 | } next(idx(i,j)) = (for { 72 | k <- 0 until 4 73 | } yield apply(i)(k) * other(k)(j)).sum 74 | Matrix4x4(next) 75 | } 76 | 77 | def determinant(using Numeric[T]): T = { 78 | // TODO - Use a better decomposition to compute the inverse! 79 | (m14*m23*m32*m41) - (m13*m24*m32*m41) - (m14*m22*m33*m41) + (m12*m24*m33*m41) + 80 | (m13*m22*m34*m41) - (m12*m23*m34*m41) - (m14*m23*m31*m42) + (m13*m24*m31*m42) + 81 | (m14*m21*m33*m41) - (m11*m24*m33*m42) - (m13*m21*m34*m42) + (m11*m23*m34*m42) + 82 | (m14*m22*m31*m43) - (m12*m24*m31*m43) - (m14*m21*m32*m43) + (m11*m24*m32*m43) + 83 | (m12*m21*m34*m43) - (m11*m22*m34*m43) - (m13*m22*m31*m44) + (m12*m23*m31*m44) + 84 | (m13*m21*m32*m44) - (m11*m23*m32*m44) - (m12*m21*m33*m44) + (m11*m22*m33*m44) 85 | } 86 | def transpose: Matrix4x4[T] = { 87 | new Matrix4x4[T](Array( 88 | m11, m12, m13, m14, 89 | m21, m22, m23, m24, 90 | m31, m32, m33, m34, 91 | m41, m42, m43, m44 92 | )) 93 | } 94 | def inverse(using Fractional[T]): Matrix4x4[T] = { 95 | // Stack overflow when we had 1.0f / determinant. 96 | val scale: T = summon[Fractional[T]].one / determinant 97 | // TODO - use a better decomposition to compute the inverse! 98 | val n11 = (m23*m34*m42) - (m24*m33*m42) + (m24*m32*m43) - (m22*m34*m43) - (m23*m32*m44) + (m22*m33*m44) 99 | val n12 = (m14*m33*m42) - (m13*m34*m42) - (m14*m32*m43) + (m12*m34*m43) + (m13*m32*m44) - (m12*m33*m44) 100 | val n13 = (m13*m24*m42) - (m14*m23*m42) + (m14*m22*m43) - (m12*m24*m43) - (m13*m22*m44) + (m12*m23*m44) 101 | val n14 = (m14*m23*m32) - (m13*m24*m32) - (m14*m22*m33) + (m12*m24*m33) + (m13*m22*m34) - (m12*m23*m34) 102 | val n21 = (m24*m33*m41) - (m23*m34*m41) - (m24*m31*m43) + (m21*m34*m43) + (m23*m31*m44) - (m21*m33*m44) 103 | val n22 = (m13*m34*m41) - (m14*m33*m41) + (m14*m31*m43) - (m11*m34*m43) - (m13*m31*m44) + (m11*m33*m44) 104 | val n23 = (m14*m23*m41) - (m13*m24*m41) - (m14*m21*m43) + (m11*m24*m43) + (m13*m21*m44) - (m11*m23*m44) 105 | val n24 = (m13*m24*m31) - (m14*m23*m31) + (m14*m21*m33) - (m11*m24*m33) - (m13*m21*m34) + (m11*m23*m34) 106 | val n31 = (m22*m34*m41) - (m24*m32*m41) + (m24*m31*m42) - (m21*m34*m42) - (m22*m31*m44) + (m21*m32*m44) 107 | val n32 = (m14*m32*m41) - (m12*m34*m41) - (m14*m31*m42) + (m11*m34*m42) + (m12*m31*m44) - (m11*m32*m44) 108 | val n33 = (m12*m24*m41) - (m14*m22*m41) + (m14*m21*m42) - (m11*m24*m42) - (m12*m21*m44) + (m11*m22*m44) 109 | val n34 = m14*m22*m31 - m12*m24*m31 - m14*m21*m32 + m11*m24*m32 + m12*m21*m34 - m11*m22*m34 110 | val n41 = m23*m32*m41 - m22*m33*m41 - m23*m31*m42 + m21*m33*m42 + m22*m31*m43 - m21*m32*m43 111 | val n42 = m12*m33*m41 - m13*m32*m41 + m13*m31*m42 - m11*m33*m42 - m12*m31*m43 + m11*m32*m43 112 | val n43 = m13*m22*m41 - m12*m23*m41 - m13*m21*m42 + m11*m23*m42 + m12*m21*m43 - m11*m22*m43 113 | val n44 = m12*m23*m31 - m13*m22*m31 + m13*m21*m32 - m11*m23*m32 - m12*m21*m33 + m11*m22*m33 114 | 115 | new Matrix4x4[T](Array[T]( 116 | n11*scale, n21*scale, n31*scale, n41*scale, 117 | n12*scale, n22*scale, n32*scale, n42*scale, 118 | n13*scale, n23*scale, n33*scale, n43*scale, 119 | n14*scale, n24*scale, n34*scale, n44*scale 120 | )) 121 | } 122 | 123 | def toMat3x3: Matrix3x3[T] = 124 | Matrix3x3(Array(m11, m21, m31, 125 | m12, m22, m32, 126 | m13, m23, m33)) 127 | 128 | override def toString: String = { 129 | def f(t: T): String = t.toString // TODO - use fixed-format number width... 130 | s""":|${f(m11)}|${f(m12)}|${f(m13)}|${f(m14)}| 131 | :|${f(m21)}|${f(m22)}|${f(m23)}|${f(m24)}| 132 | :|${f(m31)}|${f(m32)}|${f(m33)}|${f(m34)}| 133 | :|${f(m41)}|${f(m42)}|${f(m43)}|${f(m44)}|""".stripMargin(':') 134 | } 135 | } 136 | object Matrix4x4 { 137 | 138 | def identity[T: ClassTag : Numeric]: Matrix4x4[T] = 139 | new Matrix4x4(Array( 140 | one, zero, zero, zero, 141 | zero, one, zero, zero, 142 | zero, zero, one, zero, 143 | zero, zero, zero, one 144 | )) 145 | 146 | /** Creates a scaling matrix with the given factors: 147 | * @param x the scale factor for the x dimension 148 | * @param y the scale factor for the y dimension 149 | * @param z the scale factory for the z dimension. 150 | */ 151 | def scale[T : ClassTag : Numeric](x: T, y: T, z: T): Matrix4x4[T] = 152 | new Matrix4x4(Array( 153 | x, zero, zero, zero, 154 | zero, y, zero, zero, 155 | zero, zero, z, zero, 156 | zero, zero, zero, one 157 | )) 158 | 159 | /** 160 | * Creates an affine transform matrix with transformations in x/y/z space. 161 | */ 162 | def translate[T : ClassTag : Numeric](x: T, y: T, z: T): Matrix4x4[T] = 163 | new Matrix4x4(Array( 164 | one, zero, zero, zero, 165 | zero, one, zero, zero, 166 | zero, zero, one, zero, 167 | x, y, z, one 168 | )) 169 | /** 170 | * Creates a rotation matrix around the x axis 171 | */ 172 | def rotateX[T: ClassTag : Trigonometry : Numeric](angle: T): Matrix4[T] = { 173 | val r = angleToRadians(angle) 174 | Matrix4(Array( 175 | one, zero, zero, zero, 176 | zero, cos(r), -sin(r), zero, 177 | zero, sin(r), cos(r), zero, 178 | zero, zero, zero, one 179 | )) 180 | } 181 | 182 | /** Creates a rotation matrix around the y axis */ 183 | def rotateY[T: ClassTag : Trigonometry : Numeric](angle: T): Matrix4[T] = { 184 | val r = angleToRadians(angle) 185 | Matrix4(Array( 186 | cos(r), zero, sin(r), zero, 187 | zero, one, zero, zero, 188 | -sin(r), zero, cos(r), zero, 189 | zero, zero, zero, one 190 | )) 191 | } 192 | /** Creates a rotations matrix around the z axis */ 193 | def rotateZ[T: ClassTag : Trigonometry : Numeric](angle: T): Matrix4[T] = { 194 | val r = angleToRadians(angle) 195 | Matrix4(Array( 196 | cos(r), -sin(r), zero, zero, 197 | sin(r), cos(r), zero, zero, 198 | zero, zero, one, zero, 199 | zero, zero, zero, one 200 | )) 201 | } 202 | 203 | /** 204 | * Creates a 'view' matrix for a given eye. 205 | * 206 | * @param at The location being looked at. 207 | * @param eye The location of the eye. 208 | * @param up The direction of up according to the eye. 209 | */ 210 | def lookAt[T: ClassTag : Rootable : Fractional](at: Vec3[T], eye: Vec3[T], up: Vec3[T]): Matrix4[T] = { 211 | val zaxis = (at - eye).normalize // L = C - E 212 | val xaxis = (zaxis cross up).normalize // S = L x U 213 | val yaxis = (xaxis cross zaxis).normalize // U' = S x L 214 | // TODO - can we auto-multiple the camera/eye matrix? 215 | Matrix4[T](Array( 216 | xaxis.x, yaxis.x, -zaxis.x, zero, // (S, 0) 217 | xaxis.y, yaxis.y, -zaxis.y, zero, // (U', 0) 218 | xaxis.z, yaxis.z, -zaxis.z, zero, // (-L, 0) 219 | zero, zero, zero, one // (-E, 1) 220 | )) * Matrix4x4.translate[T](-eye.x, -eye.y, -eye.z) 221 | } 222 | /** 223 | * Creates a perspective projection matrix. 224 | * Based on gluPerspective. 225 | * 226 | * @param fovy - field of view for y (angle in degrees) 227 | * @param aspect - aspect ratio of the screen 228 | * @param zNear - distance from viewer to near clipping plane 229 | * @param zFar - distance from viewer to far clipping plane. 230 | */ 231 | def perspective[T : ClassTag : Trigonometry : Fractional](fovy: T, aspect: T, zNear: T, zFar: T): Matrix4x4[T] = { 232 | val f = (one / tan(angleToRadians(fovy / two))) 233 | val z = (zFar + zNear) / (zNear - zFar) 234 | val zFixer = (two*zFar*zNear)/(zNear - zFar) 235 | Matrix4(Array( 236 | f/aspect, zero, zero, zero, 237 | zero, f, zero, zero, 238 | zero, zero, z, zFixer, 239 | zero, zero, -one, zero 240 | )) 241 | } 242 | 243 | /** 244 | * Creates an orthographic projection matrix. 245 | * 246 | * @param left The left clipping plane (x) 247 | * @param right The right clipping plane (x) 248 | * @param top The top clipping plane (y) 249 | * @param bottom The bottom clipping plane (y) 250 | * @param near The near clipping plane (z) 251 | * @param far The far clipping plane (z) 252 | */ 253 | def ortho[T : ClassTag : Fractional](left: T, right: T, bottom: T, top: T, near: T, far: T): Matrix4[T] = { 254 | val a = two / (right - left) 255 | val b = two / (top - bottom) 256 | val c = -two / (far - near) 257 | 258 | val tx = -(right + left) / (right - left) 259 | val ty = -(top + bottom) / (top - bottom) 260 | val tz = -(far + near) / (far - near) 261 | 262 | Matrix4(Array( 263 | a, zero, zero, tx, 264 | zero, b, zero, ty, 265 | zero, zero, c, tz, 266 | zero, zero, zero, one)) 267 | } 268 | } 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /shader/src/main/scala/com/jsuereth/gl/shaders/codegen/convertors.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jsuereth.gl.shaders 18 | package codegen 19 | 20 | import quoted.QuoteContext 21 | import com.jsuereth.gl.math._ 22 | import com.jsuereth.gl.texture.{Texture2D} 23 | 24 | 25 | // Symbols for casts between scala number types that we can ignore in GLSL. 26 | val autoCasts = Set("scala.Float$.float2double","scala.Int$.int2double") 27 | 28 | // Arithmetic types we will do operator conversions for. I.e. we take scala method calls that are operator-like and turn them into GLSL operators. 29 | val arithmeticTypes = Set( 30 | "scala.Float", 31 | "scala.Int", 32 | "scala.Double", 33 | "com.jsuereth.gl.math.Vec2", 34 | "com.jsuereth.gl.math.Vec3", 35 | "com.jsuereth.gl.math.Vec4", 36 | "com.jsuereth.gl.math.Matrix4x4", 37 | "com.jsuereth.gl.math.Matrix3x3") 38 | // TODO - not all of these cross correctly... 39 | val arithmeticOps = Set("+", "-", "*", "/", ">", "<") 40 | val operatorTransforms: Map[String, String] = 41 | (for { 42 | tpe <- arithmeticTypes 43 | op <- arithmeticOps 44 | } yield s"$tpe.$op" -> op).toMap 45 | 46 | // Constructors used to create GLSL types. 47 | val typeApplyMethods = Map[String, String]( 48 | "com.jsuereth.gl.math.Vec4$.apply" -> "vec4", 49 | "com.jsuereth.gl.math.Vec3$.apply" -> "vec3", 50 | "com.jsuereth.gl.math.Vec2$.apply" -> "vec2", 51 | "com.jsuereth.gl.math.Matrix4x4$.apply" -> "mat4", 52 | "com.jsuereth.gl.math.Matrix3x3$.apply" -> "mat3" 53 | ) 54 | 55 | // Operations on java.lang.Math that are built-in on GLSL. 56 | val javaMathOperations = Set("max", "min", "pow") 57 | // Operations we've built into our math library that can be translated into GLSL. 58 | val ourMathOperations = Set("max", "min", "pow") 59 | 60 | trait StmtConvertorEnv { 61 | def recordUniform(name: String, tpe: String): Unit 62 | def recordOutput(name: String, tpe: String, location: Int): Unit 63 | def recordInput(name: String, tpe: String, location: Int): Unit 64 | def recordStruct(struct: codegen.Declaration.Struct): Unit 65 | } 66 | trait ShaderConvertorEnv extends StmtConvertorEnv { 67 | def addStatement(stmt: Statement): Unit 68 | def ast: Ast 69 | } 70 | trait VertexShaderConvertorEnv extends ShaderConvertorEnv { 71 | def fragmentEnv: ShaderConvertorEnv 72 | } 73 | 74 | class DefaultShaderConvertEnv extends ShaderConvertorEnv { 75 | private var stmts = collection.mutable.ArrayBuffer[codegen.Statement]() 76 | private var globals = collection.mutable.ArrayBuffer[codegen.Declaration]() 77 | private var names = Set[String]() 78 | private var types = Set[String]() 79 | override def addStatement(stmt: codegen.Statement): Unit = stmts.append(stmt) 80 | // TODO - record location 81 | override def recordInput(name: String, tpe: String, location: Int): Unit = 82 | if (!names(name)) { 83 | globals.append(codegen.Declaration.Input(name, tpe, Some(location))) 84 | names += name 85 | } 86 | override def recordUniform(name: String, tpe: String): Unit = 87 | if (!names(name)) { 88 | globals.append(codegen.Declaration.Uniform(name, tpe)) 89 | names += name 90 | } 91 | override def recordStruct(struct: codegen.Declaration.Struct): Unit = { 92 | if (!types(struct.name)) { 93 | globals.prepend(struct) 94 | types += struct.name 95 | } 96 | } 97 | override def recordOutput(name: String, tpe: String, location: Int): Unit = 98 | if (!names(name)) { 99 | globals.append(codegen.Declaration.Output(name, tpe, Some(location))) 100 | names += name 101 | } 102 | override def ast: codegen.Ast = { 103 | codegen.Ast(globals.toSeq :+ codegen.Declaration.Method("main", "void", stmts.toSeq)) 104 | } 105 | } 106 | 107 | /** Utilities for translating Scala into GLSL. */ 108 | class Convertors[R <: tasty.Reflection](val r: R)(using QuoteContext) { 109 | // TODO - given new API we may be able to use Type/TypeTree directly without 110 | // hiding it behind tasty.Reflection instance. 111 | import r._ 112 | 113 | def toGlslTypeFromTree(tpe: r.TypeTree): String = toGlslType(tpe.tpe) 114 | def toGlslType(tpe: r.Type): String = { 115 | import r._ 116 | // TODO - more principled version of this... 117 | // TODO - test to ensure this lines up with sizeof, vaoattribute, uniform loader, etc. 118 | if (tpe <:< typeOf[Vec2[Float]]) "vec2" 119 | else if (tpe <:< typeOf[Vec2[Int]]) "ivec2" 120 | else if (tpe <:< typeOf[Vec3[Float]]) "vec3" 121 | else if (tpe <:< typeOf[Vec3[Int]]) "ivec3" 122 | else if(tpe <:< typeOf[Vec4[Float]]) "vec4" 123 | else if(tpe <:< typeOf[Vec4[Int]]) "ivec4" 124 | else if(tpe <:< typeOf[Matrix4x4[Float]]) "mat4" 125 | else if(tpe <:< typeOf[Matrix3x3[Float]]) "mat3" 126 | else if(tpe <:< typeOf[Float]) "float" 127 | else if(tpe <:< typeOf[Boolean]) "bool" 128 | else if(tpe <:< typeOf[Int]) "int" 129 | else if(tpe <:< typeOf[Double]) "double" 130 | else if(tpe <:< typeOf[Unit]) "void" 131 | else if(tpe <:< typeOf[Texture2D]) "sampler2D" 132 | // TODO - a real compiler errors, not a runtime exception. 133 | else throw new RuntimeException(s"Unknown GLSL type: ${tpe}") 134 | } 135 | 136 | // TODO - can we make type->glsl type a straight inline method we use in macros? 137 | def toStructMemberDef(sym: r.Symbol): StructMember = 138 | StructMember(sym.name, toGlslType(sym.tree.asInstanceOf[r.ValDef].tpt.tpe)) 139 | 140 | /** Converts a given type into a GLSL struct definition. */ 141 | def toStructDefinition(tpe: r.Type): Option[Declaration.Struct] = 142 | tpe.classSymbol map { clsSym => 143 | new Declaration.Struct(clsSym.name, clsSym.caseFields.map(toStructMemberDef).toSeq) 144 | } 145 | /** Checks whether a given type is an GLSL "opaque" type. */ 146 | def isOpaqueType(tpe: r.Type): Boolean = tpe <:< typeOf[Texture2D] 147 | /** Returns true if a given type is a "structure-like" type for the purposes of GLSL. */ 148 | def isStructType(tpe: r.Type): Boolean = tpe.classSymbol match { 149 | // TODO - more robust definition. E.g. check if ALL fields have a GLSL type. 150 | case Some(clsSym) => !clsSym.caseFields.isEmpty && !isOpaqueType(tpe) 151 | case None => false 152 | } 153 | /** Returns the first member name of a structure type, if this is a structure type. Note: This is a workaround. */ 154 | def firstStructMemberName(tpe: r.Type): Option[String] = 155 | for { 156 | struct <- toStructDefinition(tpe) 157 | member <- struct.members.headOption 158 | } yield member.name 159 | 160 | 161 | /** Extractor for detecting a "fragmentShader {}" block. */ 162 | object FragmentShader { 163 | def unapply(tree: r.Statement): Option[r.Statement] = tree match { 164 | case Inlined(Some(Apply(mth, args)), statements, expansion) if mth.symbol.fullName == "com.jsuereth.gl.shaders.DslShaderProgram.fragmentShader" => Some(expansion) 165 | case _ => None 166 | } 167 | } 168 | object Erased { 169 | def unapply(tree: r.Statement): Option[r.Tree] = 170 | tree match { 171 | case Inlined(Some(erased), stmts, blck) => Some(erased) 172 | case _ => None 173 | } 174 | } 175 | 176 | /** Extractor for val x = Input(...) */ 177 | object Input { 178 | /** Returns: name, type, location */ 179 | def unapply(tree: r.Statement): Option[(String, String, Int)] = tree match { 180 | // defined in object 181 | case ValDef(sym, tpe, Some(Erased( 182 | Apply(TypeApply(Ident("Input"), _), List(NamedArg("location", Literal(Constant(location: Int)))))) 183 | )) => 184 | Some(sym, toGlslTypeFromTree(tpe), location) 185 | // Defined in Inline.... 186 | case _ => None 187 | } 188 | } 189 | /** Extractor for: Output("name", position, value) */ 190 | object Output { 191 | /** Returns: name, value (todo - tpe + location) */ 192 | def unapply(tree: r.Statement): Option[(String, String, r.Statement)] = tree match { 193 | case Erased(Apply(TypeApply(Ident("Output"), _), List(Literal(Constant(name: String)), position, value))) => 194 | Some(name, toGlslType(value.tpe), value) 195 | case _ => None 196 | } 197 | } 198 | /** Extractor for: () */ 199 | object Uniform { 200 | /** Returns: name, type. */ 201 | def unapply(tree: r.Statement): Option[(String, String)] = tree match { 202 | case Erased(Apply(Apply(TypeApply(Ident("apply"), List(tpe)), List(a @ Ident(ref))), List())) if a.tpe <:< typeOf[Uniform[_]] => 203 | // TODO - handle uniform-style structs. 204 | if (isStructType(tpe.tpe)) None 205 | else Some(ref.toString, toGlslType(tpe.tpe)) 206 | case _ => None 207 | } 208 | } 209 | /** Extractor for: (), where we are accessing a struct type. */ 210 | object UniformStruct { 211 | /** Returns: name, type and structure definition. */ 212 | def unapply(tree: r.Statement): Option[(String, Declaration.Struct)] = tree match { 213 | case Erased(Apply(Apply(TypeApply(Ident("apply"), List(tpe)), List(a @ Ident(ref))), List())) if a.tpe <:< typeOf[Uniform[_]] && isStructType(tpe.tpe) => 214 | // TODO - handle uniform-style structs. 215 | toStructDefinition(tpe.tpe) match { 216 | case Some(struct) => Some(ref.toString, struct) 217 | case None => None 218 | } 219 | case _ => None 220 | } 221 | } 222 | /** Extractor for: gl_Position =. */ 223 | object GlPosition { 224 | // TODO - limit glPosition to calling against the appropriate type/symbol... 225 | def unapply(tree: r.Statement): Option[r.Statement] = tree match { 226 | // Called by Object 227 | case Erased(Apply(TypeApply(Ident("glPosition"), _), List(value))) => Some(value) 228 | // called within Class 229 | // TODO - update for inlined/erasure. 230 | case Apply(TypeApply(Select(_, "glPosition"), _), List(value)) => Some(value) 231 | case _ => None 232 | } 233 | } 234 | /** Detect if scala is converting between int->float->double, etc. where GLSL automatically does this. */ 235 | object SafeAutoCast { 236 | def unapply(tree: r.Statement): Option[r.Statement] = tree match { 237 | case Apply(mthd, List(arg)) if autoCasts(mthd.symbol.fullName) => Some(arg) 238 | case _ => None 239 | } 240 | } 241 | /** Detect a GLSL operator encoded in Scala. */ 242 | object Operator { 243 | /** Returns: operator, lhs, rhs. */ 244 | def unapply(tree: r.Statement): Option[(String, r.Statement, r.Statement)] = tree match { 245 | // Handle operators that have implicit params... [foo.op(bar)(implicits)] 246 | case Apply(Apply(mthd @ Select(lhs, _), List(rhs)), _) if operatorTransforms.contains(mthd.symbol.fullName) => 247 | Some((operatorTransforms(mthd.symbol.fullName), lhs, rhs)) 248 | // Handle operators without implicit params.... 249 | case Apply(mthd @ Select(lhs, _), List(rhs)) if operatorTransforms.contains(mthd.symbol.fullName) => 250 | Some((operatorTransforms(mthd.symbol.fullName), lhs, rhs)) 251 | case _ => None 252 | } 253 | } 254 | /** Detect a GLSL constructor in Scala. */ 255 | object Constructor { 256 | /** Returns: type, args */ 257 | def unapply(tree: r.Statement): Option[(String, List[r.Statement])] = tree match { 258 | case Apply(term @ Apply(_, args), _) if typeApplyMethods.contains(term.symbol.fullName) => 259 | Some((typeApplyMethods(term.symbol.fullName), args)) 260 | case _ => None 261 | } 262 | } 263 | /** Detect GLSL built-in functions. */ 264 | object BuiltinFunction { 265 | /** Returns: operation, args */ 266 | def unapply(tree: r.Statement): Option[(String, List[r.Statement])] = tree match { 267 | // Handle java.lang.Math methods (todo - figure a better way to handle all built-in methods) 268 | case Apply(Select(ref, mthd), List(lhs,rhs)) if ref.symbol.fullName == "java.lang.Math" && javaMathOperations(mthd) => 269 | Some((mthd, List(lhs, rhs))) 270 | // TODO - lookup for all normalize methods... 271 | case Apply(term @ Select(ref, "normalize"), List(_,_)) if term.symbol.fullName == "com.jsuereth.gl.math.Vec3.normalize" => 272 | Some(("normalize", List(ref))) 273 | // Handle dot products on vectors 274 | // TODO - check the symbol of the method. 275 | case Apply(Apply(mthd @ Select(lhs, "dot"), List(rhs)), _) => 276 | Some(("dot", List(lhs,rhs))) 277 | // Handle Sampler2D methods 278 | case Erased(Apply(Apply(Ident("texture"), List(ref)), List(arg))) if ref.tpe <:< typeOf[Texture2D] => 279 | Some("texture", List(ref, arg)) 280 | // Handle our built-int math operations. TODO - lock this down to NOT be so flexible... 281 | case Apply(Apply(TypeApply(Ident(mathOp), _), args), /* Implicit witnesses */_) if ourMathOperations(mathOp) => 282 | Some((mathOp, args)) 283 | case _ => None 284 | } 285 | } 286 | 287 | def constantToString(c: r.Constant): String = { 288 | import r._ 289 | c match { 290 | case Constant(null) => "null" 291 | case Constant(_: Unit) => "" 292 | case Constant(n) => n.toString 293 | } 294 | } 295 | 296 | def convertExpr(tree: r.Statement)(using env: StmtConvertorEnv): Expr = tree match { 297 | case Uniform(name, tpe) => 298 | env.recordUniform(name, tpe) 299 | Expr.Id(name) 300 | case UniformStruct(name, st) => 301 | env.recordStruct(st) 302 | env.recordUniform(name, st.name) 303 | Expr.Id(name) 304 | case SafeAutoCast(arg) => convertExpr(arg) 305 | case Operator(name, lhs, rhs) => Expr.Operator(name, convertExpr(lhs), convertExpr(rhs)) 306 | case Constructor(name, args) => Expr.MethodCall(name, args.map(convertExpr)) 307 | case BuiltinFunction(name, args) => Expr.MethodCall(name, args.map(convertExpr)) 308 | case Ident(localRef) => Expr.Id(localRef) 309 | case If(cond, lhs, rhs) => Expr.Terenary(convertExpr(cond), convertExpr(lhs), convertExpr(rhs)) 310 | // Erased methods... 311 | case Select(expr, "toFloat") => convertExpr(expr) 312 | case SafeAutoCast(arg) => convertExpr(arg) 313 | case Select(term, mthd) => Expr.Select(convertExpr(term), mthd) 314 | // TODO - actual encode literals in AST? 315 | case Literal(constant) => Expr.Id(constantToString(constant)) 316 | // TODO - error message for everything else. 317 | //case Inlined(_, _, blck) => convertExpr(blck) 318 | case _ => 319 | summon[QuoteContext].error(s"Unable to convert to expr: ${tree.show}\n\nfull tree: $tree\n\nPlease file a bug with this context.") 320 | Expr.Id("") 321 | } 322 | def convertStmt(tree: r.Statement)(using env: StmtConvertorEnv): codegen.Statement = tree match { 323 | // assign 324 | case Output(name, tpe, exp) => 325 | env.recordOutput(name, tpe, 0) // TODO - location 326 | codegen.Statement.Assign(name, convertExpr(exp)) 327 | case Input(name, tpe, location) => 328 | env.recordInput(name, tpe, location) 329 | codegen.Statement.Effect(Expr.Id("")) // TODO - don't make fake statements.... 330 | case GlPosition(value) => Statement.Assign("gl_Position", convertExpr(value)) 331 | // local variable 332 | case ValDef(sym, tpe, optExpr) => codegen.Statement.LocalVariable(sym.toString, toGlslTypeFromTree(tpe), optExpr.map(convertExpr)) 333 | //case VarDef(sym, tpe, optExpr) => Statement.LocalVariable(sym.toString, toGlslTypeFromTree(tpe), optExpr.map(convertExpr)) 334 | // effect 335 | case _ => codegen.Statement.Effect(convertExpr(tree)) 336 | } 337 | // TODO - handle helper methods and extract them separately as definitions. 338 | def walkFragmentShader(tree: r.Statement)(using env: ShaderConvertorEnv): Unit = tree match { 339 | case Inlined(None, stmts, exp) => walkFragmentShader(exp) 340 | case Block(statements, last) => 341 | for(s <- (statements :+ last)) { 342 | walkFragmentShader(s) 343 | } 344 | // Ignore when we type a block as "Unit" 345 | case Typed(block, tpe) if tpe.tpe <:< typeOf[Unit] => walkFragmentShader(block) 346 | case other => env.addStatement(convertStmt(other)) 347 | } 348 | 349 | // TODO - share some of the "walk" code with walkFragmentShader. 350 | def walkVertexShader(tree: r.Statement)(using env: VertexShaderConvertorEnv): Unit = tree match { 351 | // Ignore when we type a block as "Unit" 352 | case Typed(block, tpe) if tpe.tpe <:< typeOf[Unit] => walkVertexShader(block) 353 | case FragmentShader(exp) => 354 | // TODO - make a new environment, then unify the shaders. after walking. 355 | walkFragmentShader(exp)(using env.fragmentEnv) 356 | case Inlined(None, stmts, exp) => walkVertexShader(exp) 357 | case Block(statements, last) => 358 | for(s <- (statements :+ last)) { 359 | walkVertexShader(s) 360 | } 361 | case stmt => env.addStatement(convertStmt(stmt)) 362 | } 363 | 364 | // TODO - we need to return separate ASTs for vertex/fragment shaders. 365 | def convert(tree: r.Statement): (codegen.Ast, codegen.Ast) = { 366 | object env extends DefaultShaderConvertEnv with VertexShaderConvertorEnv { 367 | override val fragmentEnv = new DefaultShaderConvertEnv() 368 | } 369 | //summon[QuoteContext].warning(s"Found shader: $tree"); 370 | walkVertexShader(tree)(using env) 371 | TransformAst(env.ast, env.fragmentEnv.ast) 372 | } 373 | } --------------------------------------------------------------------------------