From 35c654f2b6506413863628dcfa863732b3a95d5a Mon Sep 17 00:00:00 2001 From: tga Date: Thu, 12 Oct 2023 13:25:41 +0800 Subject: [PATCH] use gfx.ts --- scripts/lib.js | 2 +- src/gfx.ts | 351 +++++++++++++++++++++++++++++++++++++++++++++++++ src/kaboom.ts | 305 +++--------------------------------------- src/types.ts | 15 ++- 4 files changed, 378 insertions(+), 295 deletions(-) create mode 100644 src/gfx.ts diff --git a/scripts/lib.js b/scripts/lib.js index cc7619a22..fb0b279ec 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -73,7 +73,7 @@ async function writeFile(path, content) { export async function genDTS() { // generate .d.ts / docs data - let dts = await fs.readFile(`${srcDir}/types.ts`, "utf-8") + const dts = await fs.readFile(`${srcDir}/types.ts`, "utf-8") const f = ts.createSourceFile( "ts", diff --git a/src/gfx.ts b/src/gfx.ts new file mode 100644 index 000000000..5490424be --- /dev/null +++ b/src/gfx.ts @@ -0,0 +1,351 @@ +import type { + ImageSource, + TextureOpt, + TexFilter, + Uniform, +} from "./types" + +import { + Mat4, + Vec2, + Color, +} from "./math" + +type GFXCtx = { + gl: WebGLRenderingContext, + onDestroy: (action: () => void) => void, + pushTexture: (ty: GLenum, tex: WebGLTexture) => void, + popTexture: (ty: GLenum) => void, + pushBuffer: (ty: GLenum, tex: WebGLBuffer) => void, + popBuffer: (ty: GLenum) => void, + pushFramebuffer: (ty: GLenum, tex: WebGLFramebuffer) => void, + popFramebuffer: (ty: GLenum) => void, + pushRenderbuffer: (ty: GLenum, tex: WebGLRenderbuffer) => void, + popRenderbuffer: (ty: GLenum) => void, + destroy: () => void, +} + +export class Texture { + + ctx: GFXCtx + src: null | ImageSource = null + glTex: WebGLTexture + width: number + height: number + + constructor(ctx: GFXCtx, w: number, h: number, opt: TextureOpt = {}) { + + this.ctx = ctx + const gl = ctx.gl + this.glTex = ctx.gl.createTexture() + ctx.onDestroy(() => this.free()) + this.bind() + + if (w && h) { + gl.texImage2D( + gl.TEXTURE_2D, + 0, gl.RGBA, + w, + h, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ) + } + + this.width = w + this.height = h + + const filter = { + "linear": gl.LINEAR, + "nearest": gl.NEAREST, + }[opt.filter] ?? gl.NEAREST + + const wrap = { + "repeat": gl.REPEAT, + "clampToEadge": gl.CLAMP_TO_EDGE, + }[opt.wrap] ?? gl.CLAMP_TO_EDGE + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap) + this.unbind() + + } + + static fromImage(ctx: GFXCtx, img: ImageSource, opt: TextureOpt = {}): Texture { + const gl = ctx.gl + const tex = new Texture(ctx, 0, 0, opt) + tex.bind() + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img) + tex.width = img.width + tex.height = img.height + tex.unbind() + tex.src = img + return tex + } + + update(img: ImageSource, x = 0, y = 0) { + const gl = this.ctx.gl + this.bind() + gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, gl.RGBA, gl.UNSIGNED_BYTE, img) + this.unbind() + } + + bind() { + this.ctx.pushTexture(this.ctx.gl.TEXTURE_2D, this.glTex) + } + + unbind() { + this.ctx.popTexture(this.ctx.gl.TEXTURE_2D) + } + + free() { + this.ctx.gl.deleteTexture(this.glTex) + } + +} + +export class FrameBuffer { + + ctx: GFXCtx + tex: Texture + glFramebuffer: WebGLFramebuffer + glRenderbuffer: WebGLRenderbuffer + + constructor(ctx: GFXCtx, w: number, h: number, opt: TextureOpt = {}) { + + this.ctx = ctx + const gl = ctx.gl + ctx.onDestroy(() => this.free()) + this.tex = new Texture(ctx, w, h, opt) + this.glFramebuffer = gl.createFramebuffer() + this.glRenderbuffer = gl.createRenderbuffer() + this.bind() + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h) + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.tex.glTex, + 0, + ) + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.DEPTH_STENCIL_ATTACHMENT, + gl.RENDERBUFFER, + this.glRenderbuffer, + ) + this.unbind() + } + + get width() { + return this.tex.width + } + + get height() { + return this.tex.height + } + + toImageData() { + const gl = this.ctx.gl + const data = new Uint8ClampedArray(this.width * this.height * 4) + this.bind() + gl.readPixels(0, 0, this.width, this.height, gl.RGBA, gl.UNSIGNED_BYTE, data) + this.unbind() + // flip vertically + const bytesPerRow = this.width * 4 + const temp = new Uint8Array(bytesPerRow) + for (let y = 0; y < (this.height / 2 | 0); y++) { + const topOffset = y * bytesPerRow + const bottomOffset = (this.height - y - 1) * bytesPerRow + temp.set(data.subarray(topOffset, topOffset + bytesPerRow)) + data.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow) + data.set(temp, bottomOffset) + } + return new ImageData(data, this.width, this.height) + } + + toDataURL() { + const canvas = document.createElement("canvas") + const ctx = canvas.getContext("2d") + canvas.width = this.width + canvas.height = this.height + ctx.putImageData(this.toImageData(), 0, 0) + return canvas.toDataURL() + } + + bind() { + const gl = this.ctx.gl + this.ctx.pushFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer) + this.ctx.pushRenderbuffer(gl.RENDERBUFFER, this.glRenderbuffer) + } + + unbind() { + const gl = this.ctx.gl + this.ctx.popFramebuffer(gl.FRAMEBUFFER) + this.ctx.popRenderbuffer(gl.RENDERBUFFER) + } + + free() { + const gl = this.ctx.gl + gl.deleteFramebuffer(this.glFramebuffer) + gl.deleteRenderbuffer(this.glRenderbuffer) + this.tex.free() + } + +} + +export class Shader { + + ctx: GFXCtx + glProgram: WebGLProgram + + constructor(ctx: GFXCtx, vert: string, frag: string, attribs: string[]) { + + this.ctx = ctx + ctx.onDestroy(() => this.free()) + + const gl = ctx.gl + const vertShader = gl.createShader(gl.VERTEX_SHADER) + const fragShader = gl.createShader(gl.FRAGMENT_SHADER) + + gl.shaderSource(vertShader, vert) + gl.shaderSource(fragShader, frag) + gl.compileShader(vertShader) + gl.compileShader(fragShader) + + const prog = gl.createProgram() + this.glProgram = prog + + gl.attachShader(prog, vertShader) + gl.attachShader(prog, fragShader) + + attribs.forEach((attrib, i) => gl.bindAttribLocation(prog, i, attrib)) + + gl.linkProgram(prog) + + if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { + + const formatShaderError = (msg: string) => { + const FMT = /^ERROR:\s0:(?\d+):\s(?.+)/ + const match = msg.match(FMT) + return { + line: Number(match.groups.line), + // seem to be a \n\0 at the end of error messages, causing unwanted line break + msg: match.groups.msg.replace(/\n\0$/, ""), + } + } + + const vertError = gl.getShaderInfoLog(vertShader) + const fragError = gl.getShaderInfoLog(fragShader) + let msg = "" + + if (vertError) { + const err = formatShaderError(vertError) + msg += `Vertex shader line ${err.line - 14}: ${err.msg}` + } + + if (fragError) { + const err = formatShaderError(fragError) + msg += `Fragment shader line ${err.line - 14}: ${err.msg}` + } + + throw new Error(msg) + + } + + gl.deleteShader(vertShader) + gl.deleteShader(fragShader) + + } + + bind() { + this.ctx.gl.useProgram(this.glProgram) + } + + unbind() { + this.ctx.gl.useProgram(null) + } + + send(uniform: Uniform) { + const gl = this.ctx.gl + for (const name in uniform) { + const val = uniform[name] + const loc = gl.getUniformLocation(this.glProgram, name) + if (typeof val === "number") { + gl.uniform1f(loc, val) + } else if (val instanceof Mat4) { + gl.uniformMatrix4fv(loc, false, new Float32Array(val.m)) + } else if (val instanceof Color) { + gl.uniform3f(loc, val.r, val.g, val.b) + } else if (val instanceof Vec2) { + gl.uniform2f(loc, val.x, val.y) + } + } + } + + free() { + this.ctx.gl.deleteProgram(this.glProgram) + } + +} + +export default (gl: WebGLRenderingContext, gopt: { + texFilter?: TexFilter, +} = {}): GFXCtx => { + + function genBindFunc(func: (ty: GLenum, item: T) => void) { + const bindings = {} + return { + cur: (ty: GLenum) => { + const stack = bindings[ty] ?? [] + return stack[stack.length - 1] + }, + push: (ty: GLenum, item: T) => { + if (!bindings[ty]) bindings[ty] = [] + const stack = bindings[ty] + stack.push(item) + func(ty, item) + }, + pop: (ty: GLenum) => { + const stack = bindings[ty] + if (!stack) throw new Error(`Unknown WebGL type: ${ty}`) + if (stack.length <= 0) throw new Error("Can't unbind texture when there's no texture bound") + stack.pop() + func(ty, stack[stack.length - 1] ?? null) + }, + } + } + + const textureBinder = genBindFunc(gl.bindTexture.bind(gl)) + const bufferBinder = genBindFunc(gl.bindBuffer.bind(gl)) + const framebufferBinder = genBindFunc(gl.bindFramebuffer.bind(gl)) + const renderbufferBinder = genBindFunc(gl.bindRenderbuffer.bind(gl)) + const gc: Array<() => void> = [] + + function onDestroy(action) { + gc.push(action) + } + + function destroy() { + gc.forEach((action) => action()) + } + + return { + gl: gl, + onDestroy, + destroy, + pushTexture: textureBinder.push, + popTexture: textureBinder.pop, + pushBuffer: bufferBinder.push, + popBuffer: bufferBinder.pop, + pushFramebuffer: framebufferBinder.push, + popFramebuffer: framebufferBinder.pop, + pushRenderbuffer: renderbufferBinder.push, + popRenderbuffer: renderbufferBinder.pop, + } + +} diff --git a/src/kaboom.ts b/src/kaboom.ts index b5bd89a78..663d21ac9 100644 --- a/src/kaboom.ts +++ b/src/kaboom.ts @@ -1,6 +1,11 @@ const VERSION = "3000.1.12" import initApp from "./app" +import initGfx, { + Texture, + FrameBuffer, + Shader, +} from "./gfx" import { sat, @@ -69,7 +74,6 @@ import type { GfxFont, RenderProps, CharTransform, - TextureOpt, ImageSource, FormattedText, FormattedChar, @@ -419,116 +423,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { preserveDrawingBuffer: true, }) - function genBindFunc(func: (ty: GLenum, item: T) => void) { - const bindings = {} - return { - cur: (ty: GLenum) => { - const stack = bindings[ty] ?? [] - return stack[stack.length - 1] - }, - push: (ty: GLenum, item: T) => { - if (!bindings[ty]) bindings[ty] = [] - const stack = bindings[ty] - stack.push(item) - func(ty, item) - }, - pop: (ty: GLenum) => { - const stack = bindings[ty] - if (!stack) throw new Error(`Unknown WebGL type: ${ty}`) - if (stack.length <= 0) throw new Error("Can't unbind texture when there's no texture bound") - stack.pop() - func(ty, stack[stack.length - 1] ?? null) - }, - } - } - - const glTextureBinder = genBindFunc(gl.bindTexture.bind(gl)) - const glFramebufferBinder = genBindFunc(gl.bindFramebuffer.bind(gl)) - const glRenderbufferBinder = genBindFunc(gl.bindRenderbuffer.bind(gl)) - - class Texture { - - src: null | ImageSource = null - glTex: WebGLTexture - width: number - height: number - - constructor(w: number, h: number, opt: TextureOpt = {}) { - - this.glTex = gl.createTexture() - gc.push(() => this.free()) - this.bind() - - if (w && h) { - gl.texImage2D( - gl.TEXTURE_2D, - 0, gl.RGBA, - w, - h, - 0, - gl.RGBA, - gl.UNSIGNED_BYTE, - null, - ) - } - - this.width = w - this.height = h - - const filter = (() => { - switch (opt.filter ?? gopt.texFilter) { - case "linear": return gl.LINEAR - case "nearest": return gl.NEAREST - default: return gl.NEAREST - } - })() - - const wrap = (() => { - switch (opt.wrap) { - case "repeat": return gl.REPEAT - case "clampToEdge": return gl.CLAMP_TO_EDGE - default: return gl.CLAMP_TO_EDGE - } - })() - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap) - this.unbind() - - } - - static fromImage(img: ImageSource, opt: TextureOpt = {}): Texture { - const tex = new Texture(0, 0, opt) - tex.bind() - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img) - tex.width = img.width - tex.height = img.height - tex.unbind() - tex.src = img - return tex - } - - update(img: ImageSource, x = 0, y = 0) { - this.bind() - gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, gl.RGBA, gl.UNSIGNED_BYTE, img) - this.unbind() - } - - bind() { - glTextureBinder.push(gl.TEXTURE_2D, this.glTex) - } - - unbind() { - glTextureBinder.pop(gl.TEXTURE_2D) - } - - free() { - gl.deleteTexture(this.glTex) - } - - } + const ggfx = initGfx(gl) class KaboomError extends Error { constructor(msg) { @@ -548,7 +443,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { this.canvas = document.createElement("canvas") this.canvas.width = w this.canvas.height = h - this.tex = Texture.fromImage(this.canvas) + this.tex = Texture.fromImage(ggfx, this.canvas) this.ctx = this.canvas.getContext("2d") } add(img: ImageSource): [Texture, Quad] { @@ -562,7 +457,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { } if (this.y + img.height > this.canvas.height) { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) - this.tex = Texture.fromImage(this.canvas) + this.tex = Texture.fromImage(ggfx, this.canvas) this.x = 0 this.y = 0 this.curHeight = 0 @@ -587,88 +482,6 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { } } - class FrameBuffer { - - tex: Texture - glFramebuffer: WebGLFramebuffer - glRenderbuffer: WebGLRenderbuffer - - constructor(w: number, h: number, opt: TextureOpt = {}) { - this.tex = new Texture(w, h, opt) - this.glFramebuffer = gl.createFramebuffer() - this.glRenderbuffer = gl.createRenderbuffer() - gc.push(() => this.free()) - this.bind() - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h) - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - this.tex.glTex, - 0, - ) - gl.framebufferRenderbuffer( - gl.FRAMEBUFFER, - gl.DEPTH_STENCIL_ATTACHMENT, - gl.RENDERBUFFER, - this.glRenderbuffer, - ) - this.unbind() - } - - get width() { - return this.tex.width - } - - get height() { - return this.tex.height - } - - toImageData() { - const data = new Uint8ClampedArray(this.width * this.height * 4) - this.bind() - gl.readPixels(0, 0, this.width, this.height, gl.RGBA, gl.UNSIGNED_BYTE, data) - this.unbind() - // flip vertically - const bytesPerRow = this.width * 4 - const temp = new Uint8Array(bytesPerRow) - for (let y = 0; y < (this.height / 2 | 0); y++) { - const topOffset = y * bytesPerRow - const bottomOffset = (this.height - y - 1) * bytesPerRow - temp.set(data.subarray(topOffset, topOffset + bytesPerRow)) - data.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow) - data.set(temp, bottomOffset) - } - return new ImageData(data, this.width, this.height) - } - - toDataURL() { - const canvas = document.createElement("canvas") - const ctx = canvas.getContext("2d") - canvas.width = this.width - canvas.height = this.height - ctx.putImageData(this.toImageData(), 0, 0) - return canvas.toDataURL() - } - - bind() { - glFramebufferBinder.push(gl.FRAMEBUFFER, this.glFramebuffer) - glRenderbufferBinder.push(gl.RENDERBUFFER, this.glRenderbuffer) - } - - unbind() { - glFramebufferBinder.pop(gl.FRAMEBUFFER) - glRenderbufferBinder.pop(gl.RENDERBUFFER) - } - - free() { - gl.deleteFramebuffer(this.glFramebuffer) - gl.deleteRenderbuffer(this.glRenderbuffer) - this.tex.free() - } - - } - const gfx = (() => { const defShader = makeShader(DEF_VERT, DEF_FRAG) @@ -676,12 +489,13 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { // a 1x1 white texture to draw raw shapes like rectangles and polygons // we use a texture for those so we can use only 1 pipeline for drawing sprites + shapes const emptyTex = Texture.fromImage( + ggfx, new ImageData(new Uint8ClampedArray([ 255, 255, 255, 255 ]), 1, 1), ) const frameBuffer = (gopt.width && gopt.height) - ? new FrameBuffer(gopt.width * pixelDensity * gscale, gopt.height * pixelDensity * gscale) - : new FrameBuffer(gl.drawingBufferWidth, gl.drawingBufferHeight) + ? new FrameBuffer(ggfx, gopt.width * pixelDensity * gscale, gopt.height * pixelDensity * gscale) + : new FrameBuffer(ggfx, gl.drawingBufferWidth, gl.drawingBufferHeight) let bgColor: null | Color = null let bgAlpha = 1 @@ -727,6 +541,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { // a checkerboard texture used for the default background const bgTex = Texture.fromImage( + ggfx, new ImageData(new Uint8ClampedArray([ 128, 128, 128, 255, 190, 190, 190, 255, @@ -1166,7 +981,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { return assets.bitmapFonts.add(name, loadImg(src) .then((img) => { return makeFont( - Texture.fromImage(img, opt), + Texture.fromImage(ggfx, img, opt), gw, gh, opt.chars ?? ASCII_CHARS, @@ -1725,96 +1540,9 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { vertSrc: string | null = DEF_VERT, fragSrc: string | null = DEF_FRAG, ): GfxShader { - const vcode = VERT_TEMPLATE.replace("{{user}}", vertSrc ?? DEF_VERT) const fcode = FRAG_TEMPLATE.replace("{{user}}", fragSrc ?? DEF_FRAG) - const vertShader = gl.createShader(gl.VERTEX_SHADER) - const fragShader = gl.createShader(gl.FRAGMENT_SHADER) - - gl.shaderSource(vertShader, vcode) - gl.shaderSource(fragShader, fcode) - gl.compileShader(vertShader) - gl.compileShader(fragShader) - - const prog = gl.createProgram() - - gc.push(() => gl.deleteProgram(prog)) - gl.attachShader(prog, vertShader) - gl.attachShader(prog, fragShader) - - gl.bindAttribLocation(prog, 0, "a_pos") - gl.bindAttribLocation(prog, 1, "a_uv") - gl.bindAttribLocation(prog, 2, "a_color") - - gl.linkProgram(prog) - - if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { - - const formatShaderError = (msg: string) => { - const FMT = /^ERROR:\s0:(?\d+):\s(?.+)/ - const match = msg.match(FMT) - return { - line: Number(match.groups.line), - // seem to be a \n\0 at the end of error messages, causing unwanted line break - msg: match.groups.msg.replace(/\n\0$/, ""), - } - } - - const vertError = gl.getShaderInfoLog(vertShader) - const fragError = gl.getShaderInfoLog(fragShader) - let msg = "" - - if (vertError) { - const err = formatShaderError(vertError) - msg += `Vertex shader line ${err.line - 14}: ${err.msg}` - } - - if (fragError) { - const err = formatShaderError(fragError) - msg += `Fragment shader line ${err.line - 14}: ${err.msg}` - } - - throw new KaboomError(msg) - - } - - gl.deleteShader(vertShader) - gl.deleteShader(fragShader) - - return { - - bind() { - gl.useProgram(prog) - }, - - unbind() { - gl.useProgram(null) - }, - - free() { - gl.deleteProgram(prog) - }, - - // TODO: support vec3 and vec4 - send(uniform: Uniform) { - for (const name in uniform) { - const val = uniform[name] - const loc = gl.getUniformLocation(prog, name) - if (typeof val === "number") { - gl.uniform1f(loc, val) - } else if (val instanceof Mat4) { - gl.uniformMatrix4fv(loc, false, new Float32Array(val.m)) - } else if (val instanceof Color) { - // TODO: opacity? - gl.uniform3f(loc, val.r, val.g, val.b) - } else if (val instanceof Vec2) { - gl.uniform2f(loc, val.x, val.y) - } - } - }, - - } - + return new Shader(ggfx, vcode, fcode, VERTEX_FORMAT.map((vert) => vert.name)) } function makeFont( @@ -2671,7 +2399,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { // TODO: customizable font tex filter const atlas: FontAtlas = fontAtlases[fontName] ?? { font: { - tex: new Texture(FONT_ATLAS_WIDTH, FONT_ATLAS_HEIGHT, { + tex: new Texture(ggfx, FONT_ATLAS_WIDTH, FONT_ATLAS_HEIGHT, { filter: opts.filter, }), map: {}, @@ -6657,6 +6385,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { gl.bindFramebuffer(gl.FRAMEBUFFER, null) // run all scattered gc events + ggfx.destroy() gc.forEach((f) => f()) // delete webgl buffers @@ -6828,7 +6557,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { updateViewport() if (!fixedSize) { gfx.frameBuffer.free() - gfx.frameBuffer = new FrameBuffer(gl.drawingBufferWidth, gl.drawingBufferHeight) + gfx.frameBuffer = new FrameBuffer(ggfx, gl.drawingBufferWidth, gl.drawingBufferHeight) gfx.width = gl.drawingBufferWidth / pixelDensity gfx.height = gl.drawingBufferHeight / pixelDensity } diff --git a/src/types.ts b/src/types.ts index 94ffeda97..637d28ebe 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3104,16 +3104,19 @@ export type TextureOpt = { export type ImageSource = Exclude +type GFXCtx = unknown + export declare class Texture { - glTex: WebGLTexture + ctx: GFXCtx src: null | ImageSource + glTex: WebGLTexture width: number height: number - constructor(w: number, h: number, opt?: TextureOpt) - static fromImage(img: ImageSource, opt?: TextureOpt): Texture - update(img: ImageSource, x: number, y: number): void - bind(): void - unbind(): void + constructor(gfx: GFXCtx, w: number, h: number, opt?: TextureOpt) + static fromImage(ctx: GFXCtx, img: ImageSource, opt?: TextureOpt): Texture + update(img: ImageSource, x?: number, y?: number) + bind() + unbind() /** * Frees up texture memory. Call this once the texture is no longer being used to avoid memory leaks. */