Skip to content

Commit

Permalink
Merge pull request #113 from scenerygraphics/font-fixes
Browse files Browse the repository at this point in the history
* `FontBoard` has been renamed to `TextBoard`
* fixes signed distance field font rendering for both OpenGL and Vulkan (though it could need a bit more polishing for better filtering, etc)
* introduces a GPU compatibility matrix in the README
* if `TextBoard.fontFamily` contains a dot (e.g. "SourceSans.ttf"), the corresponding TTF font file will be searched for in the classpath and loaded; if it cannot be found, the system default font will be used
* Kotlin has been bumped to 1.1.60
* when running OpenGLRenderer on a Retina display, `Renderer.SupersamplingFactor` will be set to 0.5 to improve performance
  • Loading branch information
skalarproduktraum authored Nov 16, 2017
2 parents b40142a + 7021f99 commit 9440da6
Show file tree
Hide file tree
Showing 21 changed files with 363 additions and 292 deletions.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ __Hello, this is the master branch, where development happens and stuff breaks._

![Blood Cells Example](https://ulrik.is/scenery-bloodcells.gif)


## Synopsis

scenery is a scenegraphing and rendering library. It allows you to quickly create high-quality 3D visualisations based on mesh data. scenery contains both a OpenGL 4.1 and Vulkan renderer. The rendering pipelines of both renderers are configurable using YAML files, so it's easy to switch between e.g. [Forward Shading](./src/main/resources/graphics/scenery/backends/ForwardShading.yml) and [Deferred Shading](./src/main/resources/graphics/scenery/backends/DeferredShading.yml), as well as [stereo rendering](./src/main/resources/graphics/scenery/backends/DeferredShadingStereo.yml). Rendering pipelines can be switched on-the-fly.
Expand All @@ -27,7 +28,7 @@ Some of the examples need additional meshes, which are not part of the repositor

* Scala - [@Sciss](https://github.com/Sciss) has translated the Kotlin and Java examples to Scala, [https://github.com/Sciss/SceneryScalaExamples](https://github.com/Sciss/SceneryScalaExamples)

## Key bindings
## Default Key bindings

Most of the demos use the following key bindings:

Expand Down Expand Up @@ -111,3 +112,27 @@ compile group: 'net.clearvolume', name: 'cleargl', version: '2.1.1'
scenery uses [slf4j](https://slf4j.org) for logging. If you use scenery in your own library and want to see scenery's messages, you need to have a logger (e.g. `slf4j-simple`) configured in your project. Check [this page](https://www.slf4j.org/manual.html) on how to do that.

To configure the logging level that scenery uses, set the system property `scenery.LogLevel` to `info` (default), `warn`, `debug` or `trace`. Be advised that both `debug` and `trace` produce a lot of output and thereby negatively affect performance.

## GPU compatibility

scenery has been tested with a number of different systems and GPUs. If you have a setup that is not listed in the following table - or marked as untested - please submit a PR with the setup added.

✅ Works
⛔ Does not work
⬜ Untested

| GPU | Windows, OpenGL | Windows, Vulkan | Linux, OpenGL | Linux, Vulkan | Mac OS X, OpenGL |
|:--|:--|:--|:--|:--|:--|
| AMD Radeon R9 390 (Hawaii Pro) ||||||
| AMD Radeon R9 Nano (Fiji XT) ||||||
| AMD Radeon R9 M930X (Strato Pro) ||||||
| AMD FirePro W9100 (Hawaii XT) ||||||
| Nvidia Geforce Titan X (Maxwell) ||||||
| Nvidia Titan Xp (Pascal) ||||||
| Nvidia Geforce 1080 Ti (Pascal) ||||||
| Nvidia Geforce 1070 (Pascal) ||||||
| Nvidia Geforce 960 (Maxwell) ||||||
| Nvidia Quadro K6000 (Kepler) ||||||
| Nvidia Geforce 750M (Maxwell) ||||||

Please also note that Nvidia's Vulkan drivers before version 382.33 have a bug that prevents scenery's Vulkan renderer from working correctly.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
<scijava-common.version>2.65.0</scijava-common.version>
<scijava.jvm.version>1.8</scijava.jvm.version>
<javac.target>1.8</javac.target>
<kotlin.version>1.1.51</kotlin.version>
<kotlin.version>1.1.60</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
<dokka.version>0.9.15</dokka.version>
Expand Down
11 changes: 5 additions & 6 deletions src/main/kotlin/graphics/scenery/BoundingBox.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package graphics.scenery

import cleargl.GLVector
import graphics.scenery.utils.LazyLogger
import java.util.*

/**
Expand All @@ -20,7 +19,7 @@ import java.util.*
*/
open class BoundingBox : Mesh() {
var boundingCoords = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
var labels = HashMap<String, FontBoard>()
var labels = HashMap<String, TextBoard>()

@ShaderProperty
var gridColor: GLVector = GLVector(1.0f, 1.0f, 1.0f)
Expand Down Expand Up @@ -88,10 +87,10 @@ open class BoundingBox : Mesh() {
this.material = ShaderMaterial(arrayListOf("DefaultDeferred.vert", "BoundingBox.frag"))

labels = hashMapOf(
"origin".to(FontBoard()),
"x".to(FontBoard()),
"y".to(FontBoard()),
"z".to(FontBoard())
"origin".to(TextBoard()),
"x".to(TextBoard()),
"y".to(TextBoard()),
"z".to(TextBoard())
)

labels.forEach { s, fontBoard ->
Expand Down
51 changes: 0 additions & 51 deletions src/main/kotlin/graphics/scenery/FontBoard.kt

This file was deleted.

2 changes: 1 addition & 1 deletion src/main/kotlin/graphics/scenery/GenericTexture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ data class GenericTexture(
/** The texture's number of channels */
var channels: Int = 4,
/** [NativeTypeEnum] declaring the data type stored in [contents] */
var type: GLTypeEnum = GLTypeEnum.Byte,
var type: GLTypeEnum = GLTypeEnum.UnsignedByte,
/** Byte contents of the texture */
@Transient var contents: ByteBuffer,
/** Shall the texture be repeated on the U/S coordinate? */
Expand Down
63 changes: 63 additions & 0 deletions src/main/kotlin/graphics/scenery/TextBoard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package graphics.scenery

import cleargl.GLVector

/**
* TextBoard is a possibly billboarded display of a string of text,
* rendered using signed-distance fields.
*
* @author Ulrik Günther <hello@ulrik.is>
* @property[font] Name of the font to use for this text board
* @property[isBillboard] Whether the board should be billboarded or not
*
* @constructor Returns a TextBoard instance, with [fontFamily] and a declared [ShaderMaterial]
*/
class TextBoard(font: String = "SourceSansPro-Regular.ttf", override var isBillboard: Boolean = false) : Mesh() {

/** The text displayed on this font board */
var text: String = ""
set(value) {
dirty = true
field = value
}

/** The font family of this font board. If reset, this will set the [dirty] flag,
* such that the renderer can recreate the signed-distance fields used for displaying.
*
* If the name contains a dot (e.g. as in "Helvetica.ttf"), scenery will attempt to load
* the font as a file from the class path.
*/
var fontFamily: String = "SourceSansPro-Regular.ttf"
set(value) {
dirty = true
field = value
}

/** The [ShaderProperty] storing whether the font board should be renderer transparently. */
@ShaderProperty var transparent: Int = 1
/** [ShaderProperty] to store the size of the used texture atlas storing the font's signed distance field */
@ShaderProperty var atlasSize = GLVector(1024.0f, 1024.0f, 0.0f, 0.0f)
/** The [ShaderProperty] storing the font's color. */
@ShaderProperty var fontColor: GLVector = GLVector(0.5f, 0.5f, 0.5f, 1.0f)
/** The [ShaderProperty] storing the background color of the font board,
* used only if [transparent] is 0. */
@ShaderProperty var backgroundColor: GLVector = GLVector(1.0f, 1.0f, 1.0f, 1.0f)

init {
name = "TextBoard"
fontFamily = font
material = ShaderMaterial(arrayListOf("DefaultForward.vert", "TextBoard.frag"))
material.blending.transparent = true
material.blending.sourceColorBlendFactor = Blending.BlendFactor.One
material.blending.destinationColorBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha
material.blending.sourceAlphaBlendFactor = Blending.BlendFactor.One
material.blending.destinationAlphaBlendFactor = Blending.BlendFactor.Zero
material.blending.colorBlending = Blending.BlendOp.add
material.blending.alphaBlending = Blending.BlendOp.add
}

/** Stringify the font board. Returns [fontFamily] used as well as the [text]. */
override fun toString(): String {
return "TextBoard ($fontFamily): $text"
}
}
33 changes: 23 additions & 10 deletions src/main/kotlin/graphics/scenery/backends/opengl/OpenGLRenderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,18 @@ class OpenGLRenderer(hub: Hub,

val flow = renderConfig.createRenderpassFlow()

val supersamplingFactor = if(settings.get<Float>("Renderer.SupersamplingFactor").toInt() == 1) {
if(cglWindow != null && ClearGLWindow.isRetina(cglWindow!!.gl)) {
logger.debug("Setting Renderer.SupersamplingFactor to 0.5, as we are rendering on a retina display.")
settings.set("Renderer.SupersamplingFactor", 0.5f)
0.5f
} else {
settings.get<Float>("Renderer.SupersamplingFactor")
}
} else {
settings.get<Float>("Renderer.SupersamplingFactor")
}

flow.map { passName ->
val passConfig = config.renderpasses[passName]!!
val pass = OpenGLRenderpass(passName, passConfig)
Expand All @@ -434,8 +446,8 @@ class OpenGLRenderer(hub: Hub,

config.rendertargets?.filter { it.key == passConfig.output }?.map { rt ->
logger.info("Creating render framebuffer ${rt.key} for pass $passName")
width = (settings.get<Float>("Renderer.SupersamplingFactor") * windowWidth).toInt()
height = (settings.get<Float>("Renderer.SupersamplingFactor") * windowHeight).toInt()
width = (supersamplingFactor * windowWidth).toInt()
height = (supersamplingFactor * windowHeight).toInt()

if (framebuffers.containsKey(rt.key)) {
logger.info("Reusing already created framebuffer")
Expand Down Expand Up @@ -893,14 +905,14 @@ class OpenGLRenderer(hub: Hub,
}

/**
* Updates a [FontBoard], in case it's fontFamily or contents have changed.
* Updates a [TextBoard], in case it's fontFamily or contents have changed.
*
* If a SDFFontAtlas has already been created for the given fontFamily, this will be used, and
* cached as well. Else, a new one will be created.
*
* @param[board] The [FontBoard] instance.
* @param[board] The [TextBoard] instance.
*/
private fun updateFontBoard(board: FontBoard) {
private fun updateTextBoard(board: TextBoard) {
val atlas = fontAtlas.getOrPut(board.fontFamily, { SDFFontAtlas(this.hub!!, board.fontFamily, maxDistance = settings.get<Int>("sdf.MaxDistance")) })
val m = atlas.createMeshForString(board.text)

Expand All @@ -911,11 +923,12 @@ class OpenGLRenderer(hub: Hub,

board.metadata.remove("OpenGLRenderer")
board.metadata.put("OpenGLRenderer", OpenGLObjectState())
board.atlasSize = GLVector(atlas.atlasWidth.toFloat(), atlas.atlasHeight.toFloat(), 0.0f, 0.0f)
initializeNode(board)

val s = getOpenGLObjectStateFromNode(board)
val texture = textureCache.getOrPut("sdf-${board.fontFamily}", {
val t = GLTexture(gl, GLTypeEnum.Float, 1,
val t = GLTexture(gl, GLTypeEnum.UnsignedByte, 1,
atlas.atlasWidth,
atlas.atlasHeight,
1,
Expand All @@ -940,8 +953,8 @@ class OpenGLRenderer(hub: Hub,
if (n is HasGeometry) {
if (n.dirty) {
if (n.lock.tryLock()) {
if (n is FontBoard) {
updateFontBoard(n)
if (n is TextBoard) {
updateTextBoard(n)
}

if (n.vertices.remaining() > 0 && n.normals.remaining() > 0) {
Expand Down Expand Up @@ -1043,7 +1056,7 @@ class OpenGLRenderer(hub: Hub,
targetOffset.offsetX + targetOffset.width, targetOffset.offsetY + targetOffset.height,
GL4.GL_DEPTH_BUFFER_BIT, GL4.GL_NEAREST)
} else {
logger.debug("Either source or target don't have a depth buffer. If blitting to window surface, this is not a problem.")
logger.trace("Either source or target don't have a depth buffer. If blitting to window surface, this is not a problem.")
}

gl.glBindFramebuffer(GL4.GL_FRAMEBUFFER, 0)
Expand Down Expand Up @@ -1742,7 +1755,7 @@ class OpenGLRenderer(hub: Hub,
name = "ShaderProperties"

if (node.useClassDerivedShader || node.material is ShaderMaterial) {
logger.info("Shader properties are: ${s.shader?.getShaderPropertyOrder()}")
logger.debug("Shader properties are: ${s.shader?.getShaderPropertyOrder()}")
s.shader?.getShaderPropertyOrder()?.forEach { name, offset ->
add(name, { node.getShaderProperty(name)!! }, offset)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ open class OpenGLShaderModule(gl: GL4, entryPoint: String, clazz: Class<*>, shad
val sourceModificationDate = Date(sourceCodeResource.openConnection().lastModified)

if(sourceModificationDate.after(spirvModificationDate)) {
logger.info("Recompiling $shaderCodePath, as source file is newer than SPV file.")
logger.debug("Recompiling $shaderCodePath, as source file is newer than SPV file.")
actualCodePath = shaderCodePath.substringBeforeLast(".spv")
codeResource = sourceCodeResource
true
Expand All @@ -88,7 +88,7 @@ open class OpenGLShaderModule(gl: GL4, entryPoint: String, clazz: Class<*>, shad
code = BufferUtils.allocateByteAndPut(codeResource.readBytes())
code.toSPIRVBytecode()
} else {
logger.info("Compiling $actualCodePath to SPIR-V...")
logger.debug("Compiling $actualCodePath to SPIR-V...")
// code needs to be compiled first
val program = TProgram()
val defaultResources = libspirvcrossj.getDefaultTBuiltInResource()
Expand Down
Loading

0 comments on commit 9440da6

Please sign in to comment.