Skip to content

Commit

Permalink
perf(troika-three-text): avoid extra draw call on double sided materi…
Browse files Browse the repository at this point in the history
…als as of Three r130

Three r130 introduced a "feature" which forces DoubleSide transparent materials to be
rendered in two draw calls instead of one; there's no advantage to this for flat planes,
and it can be a significant performance hit. This works around that by making the geometry
two planes back-to-back, always rendered as FrontSide, and emulating the intended sidedness
via drawRange.
  • Loading branch information
lojjic committed Dec 29, 2021
1 parent c8c92fa commit 6222ef3
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 3 deletions.
37 changes: 34 additions & 3 deletions packages/troika-three-text/src/GlyphsGeometry.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import {
Float32BufferAttribute,
BufferGeometry,
PlaneBufferGeometry,
InstancedBufferGeometry,
InstancedBufferAttribute,
Sphere,
Box3,
Vector3
DoubleSide,
BackSide,
} from 'three'

const GlyphsGeometry = /*#__PURE__*/(() => {
Expand All @@ -13,11 +16,32 @@ const GlyphsGeometry = /*#__PURE__*/(() => {
function getTemplateGeometry(detail) {
let geom = templateGeometries[detail]
if (!geom) {
geom = templateGeometries[detail] = new PlaneBufferGeometry(1, 1, detail, detail).translate(0.5, 0.5, 0)
// Geometry is two planes back-to-back, which will always be rendered FrontSide only but
// appear as DoubleSide by default. FrontSide/BackSide are emulated using drawRange.
// We do it this way to avoid the performance hit of two draw calls for DoubleSide materials
// introduced by Three.js in r130 - see https://github.com/mrdoob/three.js/pull/21967
const front = new PlaneBufferGeometry(1, 1, detail, detail)
const back = front.clone()
const frontAttrs = front.attributes
const backAttrs = back.attributes
const combined = new BufferGeometry()
const vertCount = frontAttrs.uv.count
for (let i = 0; i < vertCount; i++) {
backAttrs.position.array[i * 3] *= -1 // flip position x
backAttrs.normal.array[i * 3 + 2] *= -1 // flip normal z
}
;['position', 'normal', 'uv'].forEach(name => {
combined.setAttribute(name, new Float32BufferAttribute(
[...frontAttrs[name].array, ...backAttrs[name].array],
frontAttrs[name].itemSize)
)
})
combined.setIndex([...front.index.array, ...back.index.array.map(n => n + vertCount)])
combined.translate(0.5, 0.5, 0)
geom = templateGeometries[detail] = combined
}
return geom
}
const tempVec3 = new Vector3()

const glyphBoundsAttrName = 'aTroikaGlyphBounds'
const glyphIndexAttrName = 'aTroikaGlyphIndex'
Expand Down Expand Up @@ -80,6 +104,13 @@ const GlyphsGeometry = /*#__PURE__*/(() => {
// No-op; we'll sync the boundingBox proactively when needed.
}

// Since our base geometry contains triangles for both front and back sides, we can emulate
// the "side" by restricting the draw range.
setSide(side) {
const verts = this.getIndex().count
this.setDrawRange(side === BackSide ? verts / 2 : 0, side === DoubleSide ? verts : verts / 2)
}

set detail(detail) {
if (detail !== this._detail) {
this._detail = detail
Expand Down
17 changes: 17 additions & 0 deletions packages/troika-three-text/src/Text.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Color,
DoubleSide,
FrontSide,
Matrix4,
Mesh,
MeshBasicMaterial,
Expand Down Expand Up @@ -463,6 +464,22 @@ const Text = /*#__PURE__*/(() => {
if (material.isTroikaTextMaterial) {
this._prepareForRender(material)
}

// We need to force the material to FrontSide to avoid the double-draw-call performance hit
// introduced in Three.js r130: https://github.com/mrdoob/three.js/pull/21967 - The sidedness
// is instead applied via drawRange in the GlyphsGeometry.
material._hadOwnSide = material.hasOwnProperty('side')
this.geometry.setSide(material._actualSide = material.side)
material.side = FrontSide
}

onAfterRender(renderer, scene, camera, geometry, material, group) {
// Restore original material side
if (material._hadOwnSide) {
material.side = material._actualSide
} else {
delete material.side // back to inheriting from base material
}
}

/**
Expand Down

0 comments on commit 6222ef3

Please sign in to comment.