Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 73 additions & 55 deletions src/packages/renderers/renderers/webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Renderer} from './renderer.js'
import {Texture} from "../../assets/assets/index.js"
import {WebGLUtils, WebGLTexture, Framebuffers, Program, VertexArray} from "./webgl/index.js"
import {FramebufferType} from "./index.js"
import {Utils} from "../../utils/utils.js"
import {Utils, Cache} from "../../utils/index.js"

/**
* The WebGL is the basic renderer using the html5 webgl api
Expand All @@ -25,17 +25,6 @@ export class WebGL extends Renderer {

this.compiledShaders = new Map()

this.boundFramebuffer = null
this.boundVAO = null
this.boundProgram = null
this.boundTexture = {
texture: {},
unit: null
}
this.boundClearColor = null
this.boundViewport = null
this.boundBlendFunc = null
this.boundBlendEquation = null
this.uploadedCameraMatrices = new Map()
this.boundCameraMatrixStack = []

Expand All @@ -47,6 +36,48 @@ export class WebGL extends Renderer {
this.glVao = this.gl.getExtension("OES_vertex_array_object")
this.canvas.setAttribute("style", "image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: -o-crisp-edges; image-rendering: pixelated;")

this.bindings = new Map([
[
WebGLCache.VIEWPORT,
new Cache(viewport => this.gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height), Utils.shallowEquals)
],
[
WebGLCache.CLEAR_COLOR,
new Cache(color => this.gl.clearColor(...color.asNormalizedRGBAList()), Utils.shallowEquals)
],
[
WebGLCache.BLEND_EQUATION,
new Cache(blendEquation => this.gl.blendEquationSeparate(blendEquation.modeRGB, blendEquation.modeAlpha), Utils.shallowEquals)
],
[
WebGLCache.BLEND_FUNC,
new Cache(blendFunc => this.gl.blendFuncSeparate(blendFunc.srcRGB, blendFunc.dstRGB, blendFunc.srcAlpha, blendFunc.dstAlpha), Utils.shallowEquals)
],
[
WebGLCache.FRAMEBUFFER,
new Cache(framebuffer => this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer))
],
[
WebGLCache.VAO,
new Cache(vao => this.glVao.bindVertexArrayOES(vao))
],
[
WebGLCache.PROGRAM,
new Cache(program => this.gl.useProgram(program.programRef))
],
[
WebGLCache.TEXTURE_UNIT,
new Cache(unit => this.gl.activeTexture(unit))
],
...Object.keys(WebGLCache.TEXTURE).map(textureUnit => [
WebGLCache.TEXTURE[textureUnit],
new Cache(texture => {
this.cache(WebGLCache.TEXTURE_UNIT).validate(textureUnit)
this.gl.bindTexture(this.gl.TEXTURE_2D, texture)
})
])
])

this.textureProgram = new Program({renderer: this, vertexShaderSrc: WebGLUtils.vertexShaderTexture, fragmentShaderSrc: WebGLUtils.fragmentShaderTexture})
this.rectangleProgram = new Program({renderer: this, vertexShaderSrc: WebGLUtils.vertexShaderSolid, fragmentShaderSrc: WebGLUtils.fragmentShaderRectangle})
this.circleProgram = new Program({renderer: this, vertexShaderSrc: WebGLUtils.vertexShaderSolid, fragmentShaderSrc: WebGLUtils.fragmentShaderCircle})
Expand Down Expand Up @@ -140,7 +171,7 @@ export class WebGL extends Renderer {
})

this.gl.enable(this.gl.BLEND);
this.setBlendFuncSeparate({srcRGB: this.gl.SRC_ALPHA, dstRGB: this.gl.ONE_MINUS_SRC_ALPHA, srcAlpha: this.gl.ONE, dstAlpha: this.gl.ONE_MINUS_SRC_ALPHA});
this.cache(WebGLCache.BLEND_FUNC).validate({srcRGB: this.gl.SRC_ALPHA, dstRGB: this.gl.ONE_MINUS_SRC_ALPHA, srcAlpha: this.gl.ONE, dstAlpha: this.gl.ONE_MINUS_SRC_ALPHA})

super.init()
}
Expand Down Expand Up @@ -200,53 +231,22 @@ export class WebGL extends Renderer {
}
}

setViewport({x, y, width, height}) {
if ( this.boundViewport?.x !== x
|| this.boundViewport?.y !== y
|| this.boundViewport?.width !== width
|| this.boundViewport?.height !== height
) {
this.gl.viewport(x, y, width, height)
this.boundViewport = {x, y, width, height}
}
}

setClearColor(color) {
if ( this.boundClearColor?.r !== color.r
|| this.boundClearColor?.g !== color.g
|| this.boundClearColor?.b !== color.b
|| this.boundClearColor?.a !== color.a
) {
this.gl.clearColor(...color.asNormalizedRGBAList())
this.boundClearColor = color
}
}

setBlendFuncSeparate({srcRGB, dstRGB, srcAlpha, dstAlpha}) {
if (this.boundBlendFunc?.srcRGB !== srcRGB
||this.boundBlendFunc?.dstRGB !== dstRGB
||this.boundBlendFunc?.srcAlpha !== srcAlpha
||this.boundBlendFunc?.dstAlpha !== dstAlpha
) {
this.gl.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha)
this.boundBlendFunc = {srcRGB, dstRGB, srcAlpha, dstAlpha}
}
}

setBlendEquationSeperate({modeRGB, modeAlpha}) {
if (this.boundBlendEquation?.modeRGB !== modeRGB
|| this.boundBlendEquation?.modeAlpha !== modeAlpha) {
this.gl.blendEquationSeparate(modeRGB, modeAlpha)
this.boundBlendEquation = {modeRGB, modeAlpha}
}
/**
* Returns the requested cache object
* @param {WebGLCache} name
* @returns {Cache}
*/
cache(name) {
return this.bindings.get(name)
}

uploadCameraTransform() {
const currentCameraMatrix = this.boundCameraMatrixStack[this.boundCameraMatrixStack.length-1]
const boundProgram = this.cache(WebGLCache.PROGRAM).validate()

if (this.uploadedCameraMatrices.get(this.boundProgram) !== currentCameraMatrix) {
this.boundProgram.setUniformMatrix({uniform: "u_cameraMatrix", matrix: currentCameraMatrix})
this.uploadedCameraMatrices.set(this.boundProgram, currentCameraMatrix)
if (this.uploadedCameraMatrices.get(boundProgram) !== currentCameraMatrix) {
boundProgram.setUniformMatrix({uniform: "u_cameraMatrix", matrix: currentCameraMatrix})
this.uploadedCameraMatrices.set(boundProgram, currentCameraMatrix)
}
}

Expand All @@ -267,4 +267,22 @@ export class WebGL extends Renderer {
popCameraTransform() {
this.boundCameraMatrixStack.pop()
}
}

const WebGLTextureUnits = 32

/**
* A list of caches that a WebGL renderer has
* @enum {string | object}
*/
export const WebGLCache = {
VIEWPORT: "viewport",
BLEND_EQUATION: "blendEquation",
BLEND_FUNC: "blendFunc",
CLEAR_COLOR: "clearColor",
FRAMEBUFFER: "framebuffer",
VAO: "vao",
PROGRAM: "program",
TEXTURE_UNIT: "textureUnit",
TEXTURE: Object.fromEntries(Array.from({length: WebGLTextureUnits}, (_, num) => [WebGLRenderingContext.TEXTURE0 + num, `texture${num}`]))
}
36 changes: 18 additions & 18 deletions src/packages/renderers/renderers/webgl/framebuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {WebGLUtils} from "./index.js"
import {Vec2D} from "../../../vectors/vectors/index.js"
import {Vectors} from "../../../vectors/index.js"
import {Utils} from "../../../utils/index.js"
import {WebGLCache} from "../webgl.js"

export class Framebuffer extends AbstractFramebuffer {

Expand Down Expand Up @@ -46,11 +47,8 @@ export class Framebuffer extends AbstractFramebuffer {
}

bind() {
if (this.renderer.boundFramebuffer !== this) {
this.renderer.gl.bindFramebuffer(this.renderer.gl.FRAMEBUFFER, this.framebufferRef)
this.uploadedMatrices.clear()
this.renderer.boundFramebuffer = this
}
this.renderer.cache(WebGLCache.FRAMEBUFFER).validate(this.framebufferRef)
this.uploadedMatrices.clear()
}

destroy() {
Expand All @@ -67,15 +65,17 @@ export class Framebuffer extends AbstractFramebuffer {
clearColor = clearColor || new Color()

this.bind()
this.renderer.setClearColor(clearColor)
this.renderer.cache(WebGLCache.CLEAR_COLOR).validate(clearColor)
gl.clear(gl.COLOR_BUFFER_BIT)
}

_uploadMatrices() {
this.renderer.uploadCameraTransform()
if (!this.uploadedMatrices.has(this.renderer.boundProgram)) {
this.renderer.boundProgram.setUniformMatrix({uniform: "u_framebufferMatrix", matrix: this.framebufferMatrix})
this.uploadedMatrices.set(this.renderer.boundProgram, true)
const boundProgram = this.renderer.cache(WebGLCache.PROGRAM).validate()

if (this.uploadedMatrices.get(boundProgram) !== this.framebufferMatrix) {
boundProgram.setUniformMatrix({uniform: "u_framebufferMatrix", matrix: this.framebufferMatrix})
this.uploadedMatrices.set(boundProgram, this.framebufferMatrix)
}
}

Expand All @@ -96,9 +96,9 @@ export class Framebuffer extends AbstractFramebuffer {

const gl = this.renderer.gl
this.bind()
this.renderer.setViewport({x: 0, y: 0, width: this.width, height: this.height})
this.renderer.setBlendFuncSeparate(this.blendFunc)
this.renderer.setBlendEquationSeperate(this.blendEquation)
this.renderer.cache(WebGLCache.VIEWPORT).validate({x: 0, y: 0, width: this.width, height: this.height})
this.renderer.cache(WebGLCache.BLEND_FUNC).validate(this.blendFunc)
this.renderer.cache(WebGLCache.BLEND_EQUATION).validate(this.blendEquation)

this.program.use()
this._uploadMatrices()
Expand Down Expand Up @@ -135,9 +135,9 @@ export class Framebuffer extends AbstractFramebuffer {
_renderRectangle({x, y, width, height, rotation = 0, rotationPosition = new Vec2D(), color, borderWidth}) {
const gl = this.renderer.gl
this.bind()
this.renderer.setViewport({x: 0, y: 0, width: this.width, height: this.height})
this.renderer.setBlendFuncSeparate(this.blendFunc)
this.renderer.setBlendEquationSeperate(this.blendEquation)
this.renderer.cache(WebGLCache.VIEWPORT).validate({x: 0, y: 0, width: this.width, height: this.height})
this.renderer.cache(WebGLCache.BLEND_FUNC).validate(this.blendFunc)
this.renderer.cache(WebGLCache.BLEND_EQUATION).validate(this.blendEquation)

const program = this.renderer.rectangleProgram
const vao = this.renderer.rectangleVAO
Expand All @@ -164,9 +164,9 @@ export class Framebuffer extends AbstractFramebuffer {
_renderCircle({x, y, radius, rotation = 0, rotationPosition = {x:0, y:0}, color, borderWidth}) {
const gl = this.renderer.gl
this.bind()
this.renderer.setViewport({x: 0, y: 0, width: this.width, height: this.height})
this.renderer.setBlendFuncSeparate(this.blendFunc)
this.renderer.setBlendEquationSeperate(this.blendEquation)
this.renderer.cache(WebGLCache.VIEWPORT).validate({x: 0, y: 0, width: this.width, height: this.height})
this.renderer.cache(WebGLCache.BLEND_FUNC).validate(this.blendFunc)
this.renderer.cache(WebGLCache.BLEND_EQUATION).validate(this.blendEquation)

const program = this.renderer.circleProgram
const vao = this.renderer.circleVAO
Expand Down
6 changes: 2 additions & 4 deletions src/packages/renderers/renderers/webgl/program.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {WebGLUtils} from "./utils.js"
import {Utils as WarningUtils} from "../../../utils/index.js"
import {WebGLCache} from "../webgl.js"

export class Program {

Expand All @@ -25,10 +26,7 @@ export class Program {
}

use() {
if (this.renderer.boundProgram !== this) {
this.renderer.gl.useProgram(this.programRef)
this.renderer.boundProgram = this
}
this.renderer.cache(WebGLCache.PROGRAM).validate(this)
}

setIntegerUniform({uniform, value, v1, v2, v3}) {
Expand Down
14 changes: 4 additions & 10 deletions src/packages/renderers/renderers/webgl/texture.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {WebGLCache} from "../webgl.js"

export class Texture {

/**
Expand Down Expand Up @@ -37,16 +39,8 @@ export class Texture {
}
}

bind({textureUnit} = {textureUnit: this.renderer.gl.TEXTURE0}) {
if (this.renderer.boundTexture.texture[textureUnit] !== this) {
if (this.renderer.boundTexture.unit !== textureUnit) {
this.renderer.gl.activeTexture(textureUnit)
this.renderer.boundTexture.unit = textureUnit
}

this.renderer.gl.bindTexture(this.renderer.gl.TEXTURE_2D, this.textureRef)
this.renderer.boundTexture.texture[textureUnit] = this
}
bind({textureUnit = this.renderer.gl.TEXTURE0} = {}) {
this.renderer.cache(WebGLCache.TEXTURE[textureUnit]).validate(this.textureRef)
}

destroy() {
Expand Down
16 changes: 4 additions & 12 deletions src/packages/renderers/renderers/webgl/vertexarray.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {WebGLCache} from "../webgl.js"

export class VertexArray {

/**
Expand All @@ -20,21 +22,11 @@ export class VertexArray {
}

bind() {
if (this.renderer.boundVAO !== this) {
if (this.vaoRef !== null) {
this.renderer.glVao.bindVertexArrayOES(this.vaoRef)
} else {
this.setup()
}
this.renderer.boundVAO = this
}
this.renderer.cache(WebGLCache.VAO).validate(this.vaoRef)
}

unbind() {
if (this.vaoRef !== null) {
this.renderer.glVao.bindVertexArrayOES(null)
}
this.renderer.boundVAO = null
this.renderer.cache(WebGLCache.VAO).validate(null)
}

destroy() {
Expand Down
43 changes: 43 additions & 0 deletions src/packages/utils/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* A cache prevents unnecessary execution of code when a value didn't change or the result of that code isn't needed.
* Does not work when the value can be `undefined`
*/
export class Cache {
value;
#boundValue;

/**
* Creates a new cache
* @param {function(*): void} validateFunc Gets called when a new value has to be validated.
* @param {function(*, *): boolean} [equalityFunc] Gets called to compare if a new value is equal to the old one. Defaults to `===`.
* @param {*} [initialValue=undefined] The initial value. Defaults to `undefined`.
*/
constructor(validateFunc, equalityFunc = (val1, val2) => val1 === val2, initialValue = undefined) {
this.validateFunc = validateFunc
this.equalityFunc = equalityFunc
this.value = initialValue
this.#boundValue = initialValue
}

/**
* Checks if the currently set value is different from the new value and calls the validation function if needed.
* @param {*} [newValue] Pass a new value without having to set `value` separately.
*/
validate(newValue) {
if (newValue !== undefined) this.value = newValue

if (!this.equalityFunc(this.value, this.#boundValue)) {
this.validateFunc(this.value)
this.#boundValue = this.value
}

return this.value
}

/**
* Resets the bound value and forces the next validation to run the validation function
*/
invalidate() {
this.#boundValue = undefined
}
}
3 changes: 2 additions & 1 deletion src/packages/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {Utils} from './utils.js'
import {Cache} from "./cache.js"

export {Utils}
export {Utils, Cache}
Loading