gl renderer: proper block cursor, status screen,

special character rendering
webgl-renderer
cpsdqs 7 years ago
parent c89d3d2254
commit da05ec7a2d
Signed by untrusted user: cpsdqs
GPG Key ID: 3F59586BB7448DD1
  1. 144
      js/term/font_cache.js
  2. 96
      js/term/webgl_renderer.js

@ -42,7 +42,7 @@ module.exports = class GLFontCache {
ctx.textAlign = 'center' ctx.textAlign = 'center'
ctx.textBaseline = 'middle' ctx.textBaseline = 'middle'
ctx.fillStyle = 'white' ctx.fillStyle = 'white'
ctx.fillText(character, cellSize.width * 1.5, cellSize.height * 1.5) this.drawCharacter(character)
let imageData = ctx.getImageData(0, 0, width, height) let imageData = ctx.getImageData(0, 0, width, height)
@ -56,4 +56,146 @@ module.exports = class GLFontCache {
return texture return texture
} }
drawCharacter (character) {
const { ctx, cellSize } = this
let screenX = cellSize.width
let screenY = cellSize.height
let codePoint = character.codePointAt(0)
if (codePoint >= 0x2580 && codePoint <= 0x259F) {
// block elements
ctx.beginPath()
const left = screenX
const top = screenY
const cw = cellSize.width
const ch = cellSize.height
const c2w = cellSize.width / 2
const c2h = cellSize.height / 2
// http://www.fileformat.info/info/unicode/block/block_elements/utf8test.htm
// 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
// 0x2580 ▀ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▉ ▊ ▋ ▌ ▍ ▎ ▏
// 0x2590 ▐ ░ ▒ ▓ ▔ ▕ ▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟
if (codePoint === 0x2580) {
// upper half block >▀<
ctx.rect(left, top, cw, c2h)
} else if (codePoint <= 0x2588) {
// lower n eighth block (increasing) >▁< to >█<
let offset = (1 - (codePoint - 0x2580) / 8) * ch
ctx.rect(left, top + offset, cw, ch - offset)
} else if (codePoint <= 0x258F) {
// left n eighth block (decreasing) >▉< to >▏<
let offset = (codePoint - 0x2588) / 8 * cw
ctx.rect(left, top, cw - offset, ch)
} else if (codePoint === 0x2590) {
// right half block >▐<
ctx.rect(left + c2w, top, c2w, ch)
} else if (codePoint <= 0x2593) {
// shading >░< >▒< >▓<
// dot spacing by dividing cell size by a constant. This could be
// reworked to always return a whole number, but that would require
// prime factorization, and doing that without a loop would let you
// take over the world, which is not within the scope of this project.
let dotSpacingX, dotSpacingY, dotSize
if (codePoint === 0x2591) {
dotSpacingX = cw / 4
dotSpacingY = ch / 10
dotSize = 1
} else if (codePoint === 0x2592) {
dotSpacingX = cw / 6
dotSpacingY = cw / 10
dotSize = 1
} else if (codePoint === 0x2593) {
dotSpacingX = cw / 4
dotSpacingY = cw / 7
dotSize = 2
}
let alignRight = false
for (let dy = 0; dy < ch; dy += dotSpacingY) {
for (let dx = 0; dx < cw; dx += dotSpacingX) {
// prevent overflow
let dotSizeY = Math.min(dotSize, ch - dy)
ctx.rect(left + (alignRight ? cw - dx - dotSize : dx), top + dy, dotSize, dotSizeY)
}
alignRight = !alignRight
}
} else if (codePoint === 0x2594) {
// upper one eighth block >▔<
ctx.rect(left, top, cw, ch / 8)
} else if (codePoint === 0x2595) {
// right one eighth block >▕<
ctx.rect(left + (7 / 8) * cw, top, cw / 8, ch)
} else if (codePoint === 0x2596) {
// left bottom quadrant >▖<
ctx.rect(left, top + c2h, c2w, c2h)
} else if (codePoint === 0x2597) {
// right bottom quadrant >▗<
ctx.rect(left + c2w, top + c2h, c2w, c2h)
} else if (codePoint === 0x2598) {
// left top quadrant >▘<
ctx.rect(left, top, c2w, c2h)
} else if (codePoint === 0x2599) {
// left chair >▙<
ctx.rect(left, top, c2w, ch)
ctx.rect(left + c2w, top + c2h, c2w, c2h)
} else if (codePoint === 0x259A) {
// quadrants lt rb >▚<
ctx.rect(left, top, c2w, c2h)
ctx.rect(left + c2w, top + c2h, c2w, c2h)
} else if (codePoint === 0x259B) {
// left chair upside down >▛<
ctx.rect(left, top, c2w, ch)
ctx.rect(left + c2w, top, c2w, c2h)
} else if (codePoint === 0x259C) {
// right chair upside down >▜<
ctx.rect(left, top, cw, c2h)
ctx.rect(left + c2w, top + c2h, c2w, c2h)
} else if (codePoint === 0x259D) {
// right top quadrant >▝<
ctx.rect(left + c2w, top, c2w, c2h)
} else if (codePoint === 0x259E) {
// quadrants lb rt >▞<
ctx.rect(left, top + c2h, c2w, c2h)
ctx.rect(left + c2w, top, c2w, c2h)
} else if (codePoint === 0x259F) {
// right chair upside down >▟<
ctx.rect(left, top + c2h, c2w, c2h)
ctx.rect(left + c2w, top, c2w, ch)
}
ctx.fill()
} else if (codePoint >= 0xE0B0 && codePoint <= 0xE0B3) {
// powerline symbols, except branch, line, and lock. Basically, just the triangles
ctx.beginPath()
if (codePoint === 0xE0B0 || codePoint === 0xE0B1) {
// right-pointing triangle
ctx.moveTo(screenX, screenY)
ctx.lineTo(screenX + cellSize.width, screenY + cellSize.height / 2)
ctx.lineTo(screenX, screenY + cellSize.height)
} else if (codePoint === 0xE0B2 || codePoint === 0xE0B3) {
// left-pointing triangle
ctx.moveTo(screenX + cellSize.width, screenY)
ctx.lineTo(screenX, screenY + cellSize.height / 2)
ctx.lineTo(screenX + cellSize.width, screenY + cellSize.height)
}
if (codePoint % 2 === 0) {
// triangle
ctx.fill()
} else {
// chevron
ctx.strokeStyle = ctx.fillStyle
ctx.stroke()
}
} else {
// Draw other characters using the text renderer
ctx.fillText(character, cellSize.width * 1.5, cellSize.height * 1.5)
}
}
} }

@ -73,8 +73,10 @@ module.exports = class WebGLRenderer extends EventEmitter {
} }
resetDrawn (width, height) { resetDrawn (width, height) {
this.gl.clearColor(0, 0, 0, 1) this.gl.clearColor(...this.getColor(this.defaultBG))
if (width && height) {
this.gl.viewport(0, 0, width, height) this.gl.viewport(0, 0, width, height)
}
this.gl.enable(this.gl.BLEND) this.gl.enable(this.gl.BLEND)
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA) this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA)
} }
@ -229,10 +231,16 @@ precision mediump float;
attribute vec2 position; attribute vec2 position;
uniform mat4 projection; uniform mat4 projection;
uniform vec2 char_pos; uniform vec2 char_pos;
uniform bool clip;
varying highp vec2 tex_coord; varying highp vec2 tex_coord;
void main() { void main() {
if (clip) {
gl_Position = projection * vec4(char_pos + position, 0.0, 1.0);
tex_coord = position / 3.0 + vec2(1.0 / 3.0, 1.0 / 3.0);
} else {
gl_Position = projection * vec4(char_pos - vec2(1.0, 1.0) + 3.0 * position, 0.0, 1.0); gl_Position = projection * vec4(char_pos - vec2(1.0, 1.0) + 3.0 * position, 0.0, 1.0);
tex_coord = position; tex_coord = position;
}
} }
`, ` `, `
precision highp float; precision highp float;
@ -266,7 +274,8 @@ void main() {
projection: gl.getUniformLocation(charShader, 'projection'), projection: gl.getUniformLocation(charShader, 'projection'),
charPos: gl.getUniformLocation(charShader, 'char_pos'), charPos: gl.getUniformLocation(charShader, 'char_pos'),
color: gl.getUniformLocation(charShader, 'color'), color: gl.getUniformLocation(charShader, 'color'),
texture: gl.getUniformLocation(charShader, 'texture') texture: gl.getUniformLocation(charShader, 'texture'),
clip: gl.getUniformLocation(charShader, 'clip')
} }
} }
@ -292,7 +301,32 @@ void main() {
} }
draw (reason) { draw (reason) {
const { gl, width, height, padding, devicePixelRatio } = this const { gl, width, height, padding, devicePixelRatio, statusScreen } = this
let { screen, screenFG, screenBG, screenAttrs } = this
if (statusScreen) {
this.startDrawLoop()
screen = new Array(width * height).fill(' ')
screenFG = new Array(width * height).fill(this.defaultFG)
screenBG = new Array(width * height).fill(this.defaultBG)
screenAttrs = new Array(width * height).fill(ATTR_FG | ATTR_BG)
let text = statusScreen.title
for (let i = 0; i < Math.min(width * height, text.length); i++) {
screen[i] = text[i]
}
if (statusScreen.loading) {
let t = Date.now() / 1000
for (let i = width; i < Math.min(width * height, width + 8); i++) {
let offset = ((t * 12) - i) % 12
let value = Math.max(0.2, 1 - offset / 3) * 255
screenFG[i] = 256 + value + (value << 8) + (value << 16)
screen[i] = '*'
}
}
}
if (this.debug && this._debug) this._debug.drawStart(reason) if (this.debug && this._debug) this._debug.drawStart(reason)
@ -323,12 +357,15 @@ void main() {
this.cursor.y === y && this.cursor.y === y &&
this.cursor.visible this.cursor.visible
let text = this.screen[cell] let text = screen[cell]
let fg = this.screenFG[cell] | 0 let fg = screenFG[cell] | 0
let bg = this.screenBG[cell] | 0 let bg = screenBG[cell] | 0
let attrs = this.screenAttrs[cell] | 0 let attrs = screenAttrs[cell] | 0
let inSelection = this.screenSelection[cell] let inSelection = this.screenSelection[cell]
if (!(cell in screen)) continue
if (statusScreen) isCursor = false
// let isDefaultBG = false // let isDefaultBG = false
if (!(attrs & ATTR_FG)) fg = this.defaultFG if (!(attrs & ATTR_FG)) fg = this.defaultFG
@ -351,9 +388,6 @@ void main() {
bg = -2 bg = -2
} }
// TODO: actual cursor
if (isCursor) [fg, bg] = [bg, fg]
gl.uniform2f(this.bgShader.uniforms.charPos, x, y) gl.uniform2f(this.bgShader.uniforms.charPos, x, y)
gl.uniform4f(this.bgShader.uniforms.color, ...this.getColor(bg)) gl.uniform4f(this.bgShader.uniforms.color, ...this.getColor(bg))
@ -369,7 +403,7 @@ void main() {
this.drawSquare() this.drawSquare()
if (text.trim()) { if (text.trim() || isCursor) {
let fontIndex = 0 let fontIndex = 0
if (attrs & ATTR_BOLD) fontIndex |= 1 if (attrs & ATTR_BOLD) fontIndex |= 1
if (attrs & ATTR_ITALIC) fontIndex |= 2 if (attrs & ATTR_ITALIC) fontIndex |= 2
@ -377,7 +411,7 @@ void main() {
let type = font + text let type = font + text
if (!textCells[type]) textCells[type] = [] if (!textCells[type]) textCells[type] = []
textCells[type].push({ x, y, text, font, fg }) textCells[type].push({ x, y, text, font, fg, bg, isCursor })
} }
} }
@ -391,18 +425,54 @@ void main() {
gl.uniform1i(this.charShader.uniforms.texture, 0) gl.uniform1i(this.charShader.uniforms.texture, 0)
for (let cell of textCells[key]) { for (let cell of textCells[key]) {
let { x, y, fg } = cell let { x, y, fg, bg, isCursor } = cell
gl.uniform2f(this.charShader.uniforms.charPos, x, y) gl.uniform2f(this.charShader.uniforms.charPos, x, y)
gl.uniform4f(this.charShader.uniforms.color, ...this.getColor(fg)) gl.uniform4f(this.charShader.uniforms.color, ...this.getColor(fg))
this.drawSquare() this.drawSquare()
if (isCursor) {
if (fg === bg) {
fg = 7
bg = 0
}
this.useShader(this.bgShader, projection)
gl.uniform2f(this.bgShader.uniforms.extend, 0, 0)
gl.uniform2f(this.bgShader.uniforms.charPos, x, y)
gl.uniform4f(this.bgShader.uniforms.color, ...this.getColor(fg))
this.drawSquare()
this.useShader(this.charShader, projection)
gl.uniform4f(this.charShader.uniforms.color, ...this.getColor(bg))
gl.uniform1i(this.charShader.uniforms.clip, true)
this.drawSquare()
gl.uniform1i(this.charShader.uniforms.clip, false)
}
} }
} }
if (this.debug && this._debug) this._debug.drawEnd() if (this.debug && this._debug) this._debug.drawEnd()
} }
startDrawLoop () {
if (this._drawTimerThread) return
let threadID = Math.random().toString(36)
this._drawTimerThread = threadID
this.drawTimerLoop(threadID)
}
stopDrawLoop () {
this._drawTimerThread = null
}
drawTimerLoop (threadID) {
if (!threadID || threadID !== this._drawTimerThread) return
window.requestAnimationFrame(() => this.drawTimerLoop(threadID))
this.draw('draw-loop')
}
static colorToRGBA (color) { static colorToRGBA (color) {
color = color.substr(1) color = color.substr(1)
if (color.length === 3) { if (color.length === 3) {

Loading…
Cancel
Save