canvas-ultrafast 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -0
- package/dist/canvas-ultrafast.es.js +963 -0
- package/dist/canvas-ultrafast.es.js.br +0 -0
- package/dist/canvas-ultrafast.es.js.gz +0 -0
- package/dist/canvas-ultrafast.es.js.zst +0 -0
- package/dist/canvas-ultrafast.umd.js +47 -0
- package/dist/canvas-ultrafast.umd.js.br +0 -0
- package/dist/canvas-ultrafast.umd.js.gz +0 -0
- package/dist/canvas-ultrafast.umd.js.zst +0 -0
- package/dist/index.d.ts +229 -0
- package/package.json +56 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
(function(b,A){typeof exports=="object"&&typeof module<"u"?A(exports):typeof define=="function"&&define.amd?define(["exports"],A):(b=typeof globalThis<"u"?globalThis:b||self,A(b.CanvasUltrafast={}))})(this,(function(b){"use strict";class A{d=[];b={};a(t,...e){this.d.push({type:"method",name:t,args:e})}c(t,e){this.b[t]=e,this.d.push({type:"property",name:t,value:e})}takeCommands(){if(this.d.length===0)return[];const t=this.d;return this.d=[],t}save(){this.a("save")}restore(){this.a("restore")}scale(t,e){this.a("scale",t,e)}rotate(t){this.a("rotate",t)}translate(t,e){this.a("translate",t,e)}transform(t,e,s,i,r,a){this.a("transform",t,e,s,i,r,a)}setTransform(t,e,s,i,r,a){this.a("setTransform",t,e,s,i,r,a)}resetTransform(){this.a("resetTransform")}clearRect(t,e,s,i){this.a("clearRect",t,e,s,i)}fillRect(t,e,s,i){this.a("fillRect",t,e,s,i)}strokeRect(t,e,s,i){this.a("strokeRect",t,e,s,i)}fillText(t,e,s,i){this.a("fillText",...i!==void 0?[t,e,s,i]:[t,e,s])}strokeText(t,e,s,i){this.a("strokeText",...i!==void 0?[t,e,s,i]:[t,e,s])}beginPath(){this.a("beginPath")}closePath(){this.a("closePath")}moveTo(t,e){this.a("moveTo",t,e)}lineTo(t,e){this.a("lineTo",t,e)}bezierCurveTo(t,e,s,i,r,a){this.a("bezierCurveTo",t,e,s,i,r,a)}quadraticCurveTo(t,e,s,i){this.a("quadraticCurveTo",t,e,s,i)}arc(t,e,s,i,r,a){this.a("arc",...a!==void 0?[t,e,s,i,r,a]:[t,e,s,i,r])}arcTo(t,e,s,i,r){this.a("arcTo",t,e,s,i,r)}ellipse(t,e,s,i,r,a,h,o){this.a("ellipse",...o!==void 0?[t,e,s,i,r,a,h,o]:[t,e,s,i,r,a,h])}rect(t,e,s,i){this.a("rect",t,e,s,i)}fill(){this.a("fill")}stroke(){this.a("stroke")}clip(){this.a("clip")}set fillStyle(t){this.c("fillStyle",t)}set strokeStyle(t){this.c("strokeStyle",t)}set lineWidth(t){this.c("lineWidth",t)}set lineCap(t){this.c("lineCap",t)}set lineJoin(t){this.c("lineJoin",t)}set miterLimit(t){this.c("miterLimit",t)}set lineDashOffset(t){this.c("lineDashOffset",t)}set font(t){this.c("font",t)}set textAlign(t){this.c("textAlign",t)}set textBaseline(t){this.c("textBaseline",t)}set globalAlpha(t){this.c("globalAlpha",t)}set globalCompositeOperation(t){this.c("globalCompositeOperation",t)}set shadowBlur(t){this.c("shadowBlur",t)}set shadowColor(t){this.c("shadowColor",t)}set shadowOffsetX(t){this.c("shadowOffsetX",t)}set shadowOffsetY(t){this.c("shadowOffsetY",t)}get fillStyle(){return this.b.fillStyle??"#000"}get strokeStyle(){return this.b.strokeStyle??"#000"}get lineWidth(){return this.b.lineWidth??1}get lineCap(){return this.b.lineCap??"butt"}get lineJoin(){return this.b.lineJoin??"miter"}get miterLimit(){return this.b.miterLimit??10}get lineDashOffset(){return this.b.lineDashOffset??0}get font(){return this.b.font??"10px sans-serif"}get textAlign(){return this.b.textAlign??"start"}get textBaseline(){return this.b.textBaseline??"alphabetic"}get globalAlpha(){return this.b.globalAlpha??1}get globalCompositeOperation(){return this.b.globalCompositeOperation??"source-over"}get shadowBlur(){return this.b.shadowBlur??0}get shadowColor(){return this.b.shadowColor??"rgba(0, 0, 0, 0)"}get shadowOffsetX(){return this.b.shadowOffsetX??0}get shadowOffsetY(){return this.b.shadowOffsetY??0}}class p{c=[];a;b;constructor(t,e){this.b=F(t,e),this.a=new Float32Array(this.b)}save(){this.c.push(new Float32Array(this.a))}restore(){this.c.length>0&&(this.a=this.c.pop())}translate(t,e){const s=this.a;s[6]+=s[0]*t+s[3]*e,s[7]+=s[1]*t+s[4]*e}rotate(t){const e=Math.cos(t),s=Math.sin(t),i=this.a,r=i[0],a=i[1],h=i[3],o=i[4];i[0]=r*e+h*s,i[1]=a*e+o*s,i[3]=r*-s+h*e,i[4]=a*-s+o*e}scale(t,e){const s=this.a;s[0]*=t,s[1]*=t,s[3]*=e,s[4]*=e}transform(t,e,s,i,r,a){const h=this.a,o=h[0],c=h[1],l=h[3],f=h[4],d=h[6],u=h[7];h[0]=o*t+l*e,h[1]=c*t+f*e,h[3]=o*s+l*i,h[4]=c*s+f*i,h[6]=o*r+l*a+d,h[7]=c*r+f*a+u}setTransform(t,e,s,i,r,a){this.a.set(this.b),this.transform(t,e,s,i,r,a)}resetTransform(){this.a.set(this.b)}resize(t,e){this.b=F(t,e),this.a=new Float32Array(this.b),this.c=[]}getMatrix(){return this.a}}function F(n,t){return new Float32Array([2/n,0,0,0,-2/t,0,-1,1,1])}const v={black:255,white:4294967295,red:4278190335,green:8388863,blue:65535,yellow:4294902015,cyan:16777215,magenta:4278255615,orange:4289003775,transparent:0},x=new Map;function E(n){const t=x.get(n);if(t)return t;const e=P(n);return x.set(n,e),e}function P(n){const t=n.trim();if(t.charCodeAt(0)===35)return L(t);if(t.charCodeAt(0)===114)return U(t);const e=v[t.toLowerCase()];return e!==void 0?new Float32Array([(e>>>24&255)/255,(e>>>16&255)/255,(e>>>8&255)/255,(e&255)/255]):new Float32Array([0,0,0,1])}function L(n){const t=n.length;if(t===4){const e=parseInt(n[1],16),s=parseInt(n[2],16),i=parseInt(n[3],16);return new Float32Array([e*17/255,s*17/255,i*17/255,1])}if(t===7){const e=parseInt(n.slice(1,3),16),s=parseInt(n.slice(3,5),16),i=parseInt(n.slice(5,7),16);return new Float32Array([e/255,s/255,i/255,1])}if(t===9){const e=parseInt(n.slice(1,3),16),s=parseInt(n.slice(3,5),16),i=parseInt(n.slice(5,7),16),r=parseInt(n.slice(7,9),16);return new Float32Array([e/255,s/255,i/255,r/255])}if(t===5){const e=parseInt(n[1],16),s=parseInt(n[2],16),i=parseInt(n[3],16),r=parseInt(n[4],16);return new Float32Array([e*17/255,s*17/255,i*17/255,r*17/255])}return new Float32Array([0,0,0,1])}function U(n){const t=n.indexOf("("),e=n.lastIndexOf(")");if(t===-1||e===-1)return new Float32Array([0,0,0,1]);const s=n.slice(t+1,e).split(","),i=parseFloat(s[0])/255,r=parseFloat(s[1])/255,a=parseFloat(s[2])/255,h=s.length>=4?parseFloat(s[3]):1;return new Float32Array([i,r,a,h])}const B=`
|
|
2
|
+
attribute vec2 a_position;
|
|
3
|
+
uniform mat3 u_matrix;
|
|
4
|
+
void main() {
|
|
5
|
+
vec3 pos = u_matrix * vec3(a_position, 1.0);
|
|
6
|
+
gl_Position = vec4(pos.xy, 0.0, 1.0);
|
|
7
|
+
}
|
|
8
|
+
`,y=`
|
|
9
|
+
precision mediump float;
|
|
10
|
+
uniform vec4 u_color;
|
|
11
|
+
void main() {
|
|
12
|
+
gl_FragColor = u_color;
|
|
13
|
+
}
|
|
14
|
+
`,k=`
|
|
15
|
+
attribute vec2 a_position;
|
|
16
|
+
attribute vec2 a_texCoord;
|
|
17
|
+
uniform mat3 u_matrix;
|
|
18
|
+
varying vec2 v_texCoord;
|
|
19
|
+
void main() {
|
|
20
|
+
vec3 pos = u_matrix * vec3(a_position, 1.0);
|
|
21
|
+
gl_Position = vec4(pos.xy, 0.0, 1.0);
|
|
22
|
+
v_texCoord = a_texCoord;
|
|
23
|
+
}
|
|
24
|
+
`,D=`
|
|
25
|
+
precision mediump float;
|
|
26
|
+
varying vec2 v_texCoord;
|
|
27
|
+
uniform sampler2D u_texture;
|
|
28
|
+
uniform vec4 u_color;
|
|
29
|
+
void main() {
|
|
30
|
+
vec4 texel = texture2D(u_texture, v_texCoord);
|
|
31
|
+
gl_FragColor = texel * u_color;
|
|
32
|
+
}
|
|
33
|
+
`,I=`
|
|
34
|
+
attribute vec2 a_position;
|
|
35
|
+
varying vec2 v_texCoord;
|
|
36
|
+
void main() {
|
|
37
|
+
v_texCoord = a_position;
|
|
38
|
+
gl_Position = vec4(a_position * 2.0 - 1.0, 0, 1);
|
|
39
|
+
}
|
|
40
|
+
`,M=`
|
|
41
|
+
precision mediump float;
|
|
42
|
+
varying vec2 v_texCoord;
|
|
43
|
+
uniform sampler2D u_texture;
|
|
44
|
+
void main() {
|
|
45
|
+
gl_FragColor = texture2D(u_texture, v_texCoord);
|
|
46
|
+
}
|
|
47
|
+
`;class O{c;F;A;a;i=new Float32Array([0,0,0,1]);d=new Float32Array([0,0,0,1]);f=1;e=1;g="10px sans-serif";j="start";k="alphabetic";o="butt";p="miter";G=[];t=[];l=0;m=0;u=0;v=0;q;B;r;C;D;s;b;H;X=new Float32Array(4);constructor(t,e,s){this.c=t,this.F=e,this.A=s,this.a=new p(e,s),this.q=this.I(B,y,!1),this.B=this.I(k,D,!0),this.r=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,this.r),t.bufferData(t.ARRAY_BUFFER,new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1]),t.STATIC_DRAW),this.C=t.createBuffer(),this.D=t.createBuffer(),this.s=t.createTexture(),t.bindTexture(t.TEXTURE_2D,this.s),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),this.b=new OffscreenCanvas(512,128),this.H=this.b.getContext("2d"),t.enable(t.BLEND),t.blendFunc(t.SRC_ALPHA,t.ONE_MINUS_SRC_ALPHA)}executeBatch(t){for(const e of t)e.type==="property"?this.L(e.name,e.value):this.M(e.name,e.args)}resize(t,e){this.F=t,this.A=e,this.a.resize(t,e)}destroy(){const t=this.c;t.deleteBuffer(this.r),t.deleteBuffer(this.C),t.deleteBuffer(this.D),t.deleteTexture(this.s),t.deleteProgram(this.q.n),t.deleteProgram(this.B.n)}L(t,e){switch(t){case"fillStyle":this.i=E(e);break;case"strokeStyle":this.d=E(e);break;case"lineWidth":this.f=e;break;case"globalAlpha":this.e=e;break;case"font":this.g=e;break;case"textAlign":this.j=e;break;case"textBaseline":this.k=e;break;case"lineCap":this.o=e;break;case"lineJoin":this.p=e;break}}M(t,e){switch(t){case"clearRect":this.N(e[0],e[1],e[2],e[3]);break;case"fillRect":this.O(e[0],e[1],e[2],e[3]);break;case"strokeRect":this.P(e[0],e[1],e[2],e[3]);break;case"fillText":this.Q(e[0],e[1],e[2]);break;case"strokeText":this.R(e[0],e[1],e[2]);break;case"beginPath":this.t=[];break;case"closePath":(this.l!==this.u||this.m!==this.v)&&(this.t.push(this.l,this.m,this.u,this.v),this.l=this.u,this.m=this.v);break;case"moveTo":this.l=this.u=e[0],this.m=this.v=e[1];break;case"lineTo":{const s=e[0],i=e[1];this.t.push(this.l,this.m,s,i),this.l=s,this.m=i;break}case"stroke":this.S();break;case"fill":break;case"save":this.T();break;case"restore":this.U();break;case"translate":this.a.translate(e[0],e[1]);break;case"rotate":this.a.rotate(e[0]);break;case"scale":this.a.scale(e[0],e[1]);break;case"transform":this.a.transform(e[0],e[1],e[2],e[3],e[4],e[5]);break;case"setTransform":this.a.setTransform(e[0],e[1],e[2],e[3],e[4],e[5]);break;case"resetTransform":this.a.resetTransform();break}}N(t,e,s,i){const r=this.c;r.enable(r.SCISSOR_TEST),r.scissor(t,this.A-e-i,s,i),r.clearColor(0,0,0,0),r.clear(r.COLOR_BUFFER_BIT),r.disable(r.SCISSOR_TEST)}O(t,e,s,i){const r=this.c,a=this.q;r.useProgram(a.n),this.a.save(),this.a.translate(t,e),this.a.scale(s,i),r.uniformMatrix3fv(a.w,!1,this.a.getMatrix()),this.x(a.y,this.i,this.e),this.a.restore(),r.bindBuffer(r.ARRAY_BUFFER,this.r),r.enableVertexAttribArray(a.h),r.vertexAttribPointer(a.h,2,r.FLOAT,!1,0,0),r.drawArrays(r.TRIANGLES,0,6)}P(t,e,s,i){const r=this.f,a=r/2;this.z(t-a,e-a,s+r,r,this.d),this.z(t-a,e+i-a,s+r,r,this.d),this.z(t-a,e+a,r,i-r,this.d),this.z(t+s-a,e+a,r,i-r,this.d)}z(t,e,s,i,r){const a=this.c,h=this.q;a.useProgram(h.n),this.a.save(),this.a.translate(t,e),this.a.scale(s,i),a.uniformMatrix3fv(h.w,!1,this.a.getMatrix()),this.x(h.y,r,this.e),this.a.restore(),a.bindBuffer(a.ARRAY_BUFFER,this.r),a.enableVertexAttribArray(h.h),a.vertexAttribPointer(h.h,2,a.FLOAT,!1,0,0),a.drawArrays(a.TRIANGLES,0,6)}S(){const t=this.t;if(t.length===0)return;const e=this.c,s=this.q,i=Math.max(this.f/2,.5),r=t.length/4,a=new Float32Array(r*12);for(let h=0,o=0;h<t.length;h+=4){const c=t[h],l=t[h+1],f=t[h+2],d=t[h+3],u=f-c,R=d-l,_=Math.sqrt(u*u+R*R);if(_<.001)continue;const m=-R/_*i,T=u/_*i,G=c+m,H=l+T,g=c-m,S=l-T,C=f+m,w=d+T,W=f-m,Y=d-T;a[o++]=G,a[o++]=H,a[o++]=g,a[o++]=S,a[o++]=C,a[o++]=w,a[o++]=g,a[o++]=S,a[o++]=W,a[o++]=Y,a[o++]=C,a[o++]=w}e.bindBuffer(e.ARRAY_BUFFER,this.C),e.bufferData(e.ARRAY_BUFFER,a,e.DYNAMIC_DRAW),e.useProgram(s.n),e.uniformMatrix3fv(s.w,!1,this.a.getMatrix()),this.x(s.y,this.d,this.e),e.enableVertexAttribArray(s.h),e.vertexAttribPointer(s.h,2,e.FLOAT,!1,0,0),e.drawArrays(e.TRIANGLES,0,r*6)}Q(t,e,s){this.J(t,e,s,"fill")}R(t,e,s){this.J(t,e,s,"stroke")}J(t,e,s,i){const r=this.c,a=this.H;a.font=this.g,a.textAlign="left",a.textBaseline="top";const h=a.measureText(t),o=Math.ceil(h.width)+4,c=N(this.g),l=Math.ceil(c*1.5)+4;if(o<=0||l<=0)return;(this.b.width<o||this.b.height<l)&&(this.b.width=Math.max(this.b.width,o),this.b.height=Math.max(this.b.height,l),a.font=this.g,a.textAlign="left",a.textBaseline="top"),a.clearRect(0,0,this.b.width,this.b.height),i==="fill"?(a.fillStyle="white",a.fillText(t,2,2)):(a.strokeStyle="white",a.lineWidth=this.f,a.strokeText(t,2,2)),r.bindTexture(r.TEXTURE_2D,this.s),r.pixelStorei(r.UNPACK_PREMULTIPLY_ALPHA_WEBGL,1),r.texImage2D(r.TEXTURE_2D,0,r.RGBA,r.RGBA,r.UNSIGNED_BYTE,this.b),r.pixelStorei(r.UNPACK_PREMULTIPLY_ALPHA_WEBGL,0);const f=i==="fill"?this.i:this.d;let d=e-2,u=s-2;switch(this.j){case"center":d-=o/2;break;case"right":case"end":d-=o;break}switch(this.k){case"top":break;case"middle":u-=l/2;break;case"alphabetic":case"ideographic":u-=c;break;case"bottom":case"hanging":u-=l;break}this.V(d,u,o,l,o/this.b.width,l/this.b.height,f)}V(t,e,s,i,r,a,h){const o=this.c,c=this.B,l=new Float32Array([t,e,0,0,t+s,e,r,0,t,e+i,0,a,t,e+i,0,a,t+s,e,r,0,t+s,e+i,r,a]);o.bindBuffer(o.ARRAY_BUFFER,this.D),o.bufferData(o.ARRAY_BUFFER,l,o.DYNAMIC_DRAW),o.useProgram(c.n),o.uniformMatrix3fv(c.w,!1,this.a.getMatrix()),this.x(c.y,h,this.e),o.blendFunc(o.ONE,o.ONE_MINUS_SRC_ALPHA);const f=16;o.enableVertexAttribArray(c.h),o.vertexAttribPointer(c.h,2,o.FLOAT,!1,f,0),o.enableVertexAttribArray(c.E),o.vertexAttribPointer(c.E,2,o.FLOAT,!1,f,8),o.activeTexture(o.TEXTURE0),o.bindTexture(o.TEXTURE_2D,this.s),o.uniform1i(c.W,0),o.drawArrays(o.TRIANGLES,0,6),o.blendFunc(o.SRC_ALPHA,o.ONE_MINUS_SRC_ALPHA),o.disableVertexAttribArray(c.E)}T(){this.a.save(),this.G.push({i:new Float32Array(this.i),d:new Float32Array(this.d),f:this.f,e:this.e,g:this.g,j:this.j,k:this.k,o:this.o,p:this.p})}U(){this.a.restore();const t=this.G.pop();t&&(this.i=t.i,this.d=t.d,this.f=t.f,this.e=t.e,this.g=t.g,this.j=t.j,this.k=t.k,this.o=t.o,this.p=t.p)}x(t,e,s){const i=e[3]*s;this.c.uniform4f(t,e[0],e[1],e[2],i)}I(t,e,s){const i=this.c,r=this.K(i.VERTEX_SHADER,t),a=this.K(i.FRAGMENT_SHADER,e),h=i.createProgram();if(i.attachShader(h,r),i.attachShader(h,a),i.linkProgram(h),!i.getProgramParameter(h,i.LINK_STATUS))throw new Error("Shader link failed: "+i.getProgramInfoLog(h));return i.deleteShader(r),i.deleteShader(a),{n:h,w:i.getUniformLocation(h,"u_matrix"),y:i.getUniformLocation(h,"u_color"),W:s?i.getUniformLocation(h,"u_texture"):null,h:i.getAttribLocation(h,"a_position"),E:s?i.getAttribLocation(h,"a_texCoord"):-1}}K(t,e){const s=this.c,i=s.createShader(t);if(s.shaderSource(i,e),s.compileShader(i),!s.getShaderParameter(i,s.COMPILE_STATUS)){const r=s.getShaderInfoLog(i);throw s.deleteShader(i),new Error("Shader compile failed: "+r)}return i}}function N(n){const t=n.match(/(\d+(?:\.\d+)?)\s*px/);return t?parseFloat(t[1]):10}class X{b;a;h;i;d=null;c;j=0;e=1;w=2;o=!1;f;g;k=-1;constructor(t){this.a=t,this.i=new A;const e=t.getContext("webgl2",{alpha:!1,antialias:!1,desynchronized:!0,preserveDrawingBuffer:!0,powerPreference:"high-performance"});if(!e)throw new Error("WebGL2 not supported");this.b=e,e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),this.s(),this.h=new O(e,t.width,t.height),this.f=this.t(I,M),this.u(),this.v(),this.startDisplay()}getCanvasAPI(){return this.i}submitBatch(t){if(t.length===0)return;const e=this.b;e.bindFramebuffer(e.FRAMEBUFFER,this.c[this.j].l),e.viewport(0,0,this.a.width,this.a.height),this.h.executeBatch(t);const s=this.j;this.j=this.e,this.e=s,this.o=!0}startDisplay(){this.d===null&&this.p()}stopDisplay(){this.d!==null&&(cancelAnimationFrame(this.d),this.d=null)}getCanvas(){return this.a}getCanvasSize(){return{width:this.a.width,height:this.a.height}}screenshot(){return this.q(),createImageBitmap(this.a)}destroy(){this.stopDisplay();const t=this.b;this.h.destroy();for(const s of this.c)t.deleteFramebuffer(s.l),t.deleteTexture(s.m);t.deleteProgram(this.f),t.deleteBuffer(this.g);const e=t.getExtension("WEBGL_lose_context");e&&e.loseContext()}getGL(){return this.b}getReadyTexture(){return this.c[this.e].m}p(){this.d=requestAnimationFrame(()=>this.p());const t=this.i.takeCommands();t.length&&this.submitBatch(t),this.q()}q(){if(!this.o)return;const t=this.b;t.bindFramebuffer(t.FRAMEBUFFER,null),t.viewport(0,0,this.a.width,this.a.height),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.c[this.e].m),t.useProgram(this.f),t.bindBuffer(t.ARRAY_BUFFER,this.g),t.enableVertexAttribArray(this.k),t.vertexAttribPointer(this.k,2,t.FLOAT,!1,0,0),t.disable(t.BLEND),t.drawArrays(t.TRIANGLES,0,6),t.enable(t.BLEND)}s(){this.c=[this.n(),this.n(),this.n()]}n(){const t=this.b,e=this.a.width,s=this.a.height,i=t.createFramebuffer();t.bindFramebuffer(t.FRAMEBUFFER,i);const r=t.createTexture();t.bindTexture(t.TEXTURE_2D,r),t.texStorage2D(t.TEXTURE_2D,1,t.RGBA8,e,s),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,r,0);const a=t.checkFramebufferStatus(t.FRAMEBUFFER);if(a!==t.FRAMEBUFFER_COMPLETE)throw new Error("Framebuffer incomplete: 0x"+a.toString(16));return t.bindFramebuffer(t.FRAMEBUFFER,null),{l:i,m:r}}v(){const t=this.b;for(const e of this.c)t.bindFramebuffer(t.FRAMEBUFFER,e.l),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT);t.bindFramebuffer(t.FRAMEBUFFER,null),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT)}u(){const t=this.b;this.g=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,this.g),t.bufferData(t.ARRAY_BUFFER,new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1]),t.STATIC_DRAW),this.k=t.getAttribLocation(this.f,"a_position")}t(t,e){const s=this.b,i=this.r(s.VERTEX_SHADER,t),r=this.r(s.FRAGMENT_SHADER,e),a=s.createProgram();if(s.attachShader(a,i),s.attachShader(a,r),s.linkProgram(a),!s.getProgramParameter(a,s.LINK_STATUS))throw new Error("Shader link failed: "+s.getProgramInfoLog(a));return s.deleteShader(i),s.deleteShader(r),a}r(t,e){const s=this.b,i=s.createShader(t);if(s.shaderSource(i,e),s.compileShader(i),!s.getShaderParameter(i,s.COMPILE_STATUS)){const r=s.getShaderInfoLog(i);throw s.deleteShader(i),new Error("Shader compile failed: "+r)}return i}}b.CanvasAPI=A,b.MatrixStack=p,b.UltrafastRenderer=X,b.parseColor=E}));
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
export declare class CanvasAPI {
|
|
2
|
+
private _c;
|
|
3
|
+
private _cp;
|
|
4
|
+
private _m;
|
|
5
|
+
private _p;
|
|
6
|
+
/**
|
|
7
|
+
* Drain and return all buffered commands.
|
|
8
|
+
* No _ prefix: called cross-file from pipeline, must survive mangleProps.
|
|
9
|
+
*/
|
|
10
|
+
takeCommands(): CanvasCommand[];
|
|
11
|
+
save(): void;
|
|
12
|
+
restore(): void;
|
|
13
|
+
scale(x: number, y: number): void;
|
|
14
|
+
rotate(angle: number): void;
|
|
15
|
+
translate(x: number, y: number): void;
|
|
16
|
+
transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
|
17
|
+
setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
|
18
|
+
resetTransform(): void;
|
|
19
|
+
clearRect(x: number, y: number, width: number, height: number): void;
|
|
20
|
+
fillRect(x: number, y: number, width: number, height: number): void;
|
|
21
|
+
strokeRect(x: number, y: number, width: number, height: number): void;
|
|
22
|
+
fillText(text: string, x: number, y: number, maxWidth?: number): void;
|
|
23
|
+
strokeText(text: string, x: number, y: number, maxWidth?: number): void;
|
|
24
|
+
beginPath(): void;
|
|
25
|
+
closePath(): void;
|
|
26
|
+
moveTo(x: number, y: number): void;
|
|
27
|
+
lineTo(x: number, y: number): void;
|
|
28
|
+
bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void;
|
|
29
|
+
quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;
|
|
30
|
+
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
|
|
31
|
+
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;
|
|
32
|
+
ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
|
|
33
|
+
rect(x: number, y: number, width: number, height: number): void;
|
|
34
|
+
fill(): void;
|
|
35
|
+
stroke(): void;
|
|
36
|
+
clip(): void;
|
|
37
|
+
set fillStyle(value: string | CanvasGradient | CanvasPattern);
|
|
38
|
+
set strokeStyle(value: string | CanvasGradient | CanvasPattern);
|
|
39
|
+
set lineWidth(value: number);
|
|
40
|
+
set lineCap(value: CanvasLineCap);
|
|
41
|
+
set lineJoin(value: CanvasLineJoin);
|
|
42
|
+
set miterLimit(value: number);
|
|
43
|
+
set lineDashOffset(value: number);
|
|
44
|
+
set font(value: string);
|
|
45
|
+
set textAlign(value: CanvasTextAlign);
|
|
46
|
+
set textBaseline(value: CanvasTextBaseline);
|
|
47
|
+
set globalAlpha(value: number);
|
|
48
|
+
set globalCompositeOperation(value: GlobalCompositeOperation);
|
|
49
|
+
set shadowBlur(value: number);
|
|
50
|
+
set shadowColor(value: string);
|
|
51
|
+
set shadowOffsetX(value: number);
|
|
52
|
+
set shadowOffsetY(value: number);
|
|
53
|
+
get fillStyle(): string | CanvasGradient | CanvasPattern;
|
|
54
|
+
get strokeStyle(): string | CanvasGradient | CanvasPattern;
|
|
55
|
+
get lineWidth(): number;
|
|
56
|
+
get lineCap(): CanvasLineCap;
|
|
57
|
+
get lineJoin(): CanvasLineJoin;
|
|
58
|
+
get miterLimit(): number;
|
|
59
|
+
get lineDashOffset(): number;
|
|
60
|
+
get font(): string;
|
|
61
|
+
get textAlign(): CanvasTextAlign;
|
|
62
|
+
get textBaseline(): CanvasTextBaseline;
|
|
63
|
+
get globalAlpha(): number;
|
|
64
|
+
get globalCompositeOperation(): GlobalCompositeOperation;
|
|
65
|
+
get shadowBlur(): number;
|
|
66
|
+
get shadowColor(): string;
|
|
67
|
+
get shadowOffsetX(): number;
|
|
68
|
+
get shadowOffsetY(): number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Canvas API Wrapper
|
|
73
|
+
*
|
|
74
|
+
* Provides a Canvas 2D API that records commands locally without await.
|
|
75
|
+
* Commands are batched and sent to the worker for execution.
|
|
76
|
+
*/
|
|
77
|
+
export declare type CanvasCommand = {
|
|
78
|
+
type: 'property';
|
|
79
|
+
name: string;
|
|
80
|
+
value: unknown;
|
|
81
|
+
} | {
|
|
82
|
+
type: 'method';
|
|
83
|
+
name: string;
|
|
84
|
+
args: unknown[];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 3×3 Affine Transform Matrix Stack
|
|
89
|
+
*
|
|
90
|
+
* Replicates Canvas 2D's save()/restore()/translate()/rotate()/scale()
|
|
91
|
+
* as a matrix stack for WebGL uniform upload.
|
|
92
|
+
*
|
|
93
|
+
* Matrices are 3×3 column-major (for gl.uniformMatrix3fv), representing
|
|
94
|
+
* 2D affine transforms. The base matrix includes an orthographic projection
|
|
95
|
+
* that maps canvas pixel coordinates (top-left origin, Y-down) to WebGL
|
|
96
|
+
* clip space ([-1,1], Y-up).
|
|
97
|
+
*
|
|
98
|
+
* Column-major layout of a 3×3 affine matrix:
|
|
99
|
+
* [0] [3] [6] a c tx
|
|
100
|
+
* [1] [4] [7] = b d ty
|
|
101
|
+
* [2] [5] [8] 0 0 1
|
|
102
|
+
*/
|
|
103
|
+
export declare class MatrixStack {
|
|
104
|
+
private _stack;
|
|
105
|
+
private _current;
|
|
106
|
+
private _projection;
|
|
107
|
+
/**
|
|
108
|
+
* @param width Canvas width in pixels
|
|
109
|
+
* @param height Canvas height in pixels
|
|
110
|
+
*/
|
|
111
|
+
constructor(width: number, height: number);
|
|
112
|
+
/** Push current matrix onto the stack. No _ prefix: cross-file. */
|
|
113
|
+
save(): void;
|
|
114
|
+
/** Pop and restore the top matrix. No _ prefix: cross-file. */
|
|
115
|
+
restore(): void;
|
|
116
|
+
/** Translate the current matrix. No _ prefix: cross-file. */
|
|
117
|
+
translate(x: number, y: number): void;
|
|
118
|
+
/** Rotate the current matrix by angle (radians). No _ prefix: cross-file. */
|
|
119
|
+
rotate(angle: number): void;
|
|
120
|
+
/** Scale the current matrix. No _ prefix: cross-file. */
|
|
121
|
+
scale(x: number, y: number): void;
|
|
122
|
+
/**
|
|
123
|
+
* Multiply current matrix by an arbitrary 2D affine transform.
|
|
124
|
+
* Canvas 2D transform(a, b, c, d, e, f) matrix:
|
|
125
|
+
* a c e
|
|
126
|
+
* b d f
|
|
127
|
+
* 0 0 1
|
|
128
|
+
* No _ prefix: cross-file.
|
|
129
|
+
*/
|
|
130
|
+
transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
|
131
|
+
/**
|
|
132
|
+
* Reset to projection then apply the given affine transform.
|
|
133
|
+
* Canvas 2D setTransform(a, b, c, d, e, f).
|
|
134
|
+
* No _ prefix: cross-file.
|
|
135
|
+
*/
|
|
136
|
+
setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
|
137
|
+
/** Reset to the base orthographic projection. No _ prefix: cross-file. */
|
|
138
|
+
resetTransform(): void;
|
|
139
|
+
/**
|
|
140
|
+
* Update canvas dimensions (e.g., on resize).
|
|
141
|
+
* Recomputes the projection and resets the current matrix.
|
|
142
|
+
* No _ prefix: cross-file.
|
|
143
|
+
*/
|
|
144
|
+
resize(width: number, height: number): void;
|
|
145
|
+
/**
|
|
146
|
+
* Returns the current 3×3 matrix for gl.uniformMatrix3fv.
|
|
147
|
+
* No _ prefix: cross-file.
|
|
148
|
+
*/
|
|
149
|
+
getMatrix(): Float32Array;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* CSS Color Parser
|
|
154
|
+
*
|
|
155
|
+
* Converts CSS color strings to Float32Array [r, g, b, a] in [0, 1] range
|
|
156
|
+
* for use as WebGL uniform values.
|
|
157
|
+
*
|
|
158
|
+
* Supported formats:
|
|
159
|
+
* - Hex: #rgb, #rrggbb, #rrggbbaa
|
|
160
|
+
* - Functional: rgb(r, g, b), rgba(r, g, b, a)
|
|
161
|
+
* - Named: basic CSS color keywords
|
|
162
|
+
*
|
|
163
|
+
* Includes a cache for repeated lookups (the demo reuses ~10 colors).
|
|
164
|
+
*/
|
|
165
|
+
/**
|
|
166
|
+
* Parse a CSS color string into [r, g, b, a] floats in [0, 1].
|
|
167
|
+
* Returns a cached Float32Array — do NOT mutate the result.
|
|
168
|
+
*
|
|
169
|
+
* No _ prefix: called cross-file from canvas2d-shim.
|
|
170
|
+
*/
|
|
171
|
+
export declare function parseColor(css: string): Float32Array;
|
|
172
|
+
|
|
173
|
+
export declare class UltrafastRenderer {
|
|
174
|
+
private _gl;
|
|
175
|
+
private _canvas;
|
|
176
|
+
private _shim;
|
|
177
|
+
private _api;
|
|
178
|
+
private _rafId;
|
|
179
|
+
private _fbos;
|
|
180
|
+
private _writeIdx;
|
|
181
|
+
private _readyIdx;
|
|
182
|
+
private _displayIdx;
|
|
183
|
+
private _hasContent;
|
|
184
|
+
private _passthroughProgram;
|
|
185
|
+
private _quadVBO;
|
|
186
|
+
private _quadPositionLoc;
|
|
187
|
+
constructor(canvas: HTMLCanvasElement);
|
|
188
|
+
/** Get the CanvasAPI for recording draw commands. */
|
|
189
|
+
getCanvasAPI(): CanvasAPI;
|
|
190
|
+
/**
|
|
191
|
+
* Submit a batch of Canvas 2D commands to be rendered into the write FBO.
|
|
192
|
+
* After rendering, the write and ready FBOs are swapped so the display
|
|
193
|
+
* loop picks up the latest frame.
|
|
194
|
+
*/
|
|
195
|
+
submitBatch(commands: CanvasCommand[]): void;
|
|
196
|
+
/** Start the passthrough RAF display loop with auto-flush. */
|
|
197
|
+
startDisplay(): void;
|
|
198
|
+
/** Stop the passthrough RAF display loop. Last frame persists (preserveDrawingBuffer). */
|
|
199
|
+
stopDisplay(): void;
|
|
200
|
+
/** Returns the display canvas element. */
|
|
201
|
+
getCanvas(): HTMLCanvasElement;
|
|
202
|
+
/** Get canvas dimensions. */
|
|
203
|
+
getCanvasSize(): {
|
|
204
|
+
width: number;
|
|
205
|
+
height: number;
|
|
206
|
+
};
|
|
207
|
+
/** Capture the current displayed frame as an ImageBitmap. */
|
|
208
|
+
screenshot(): Promise<ImageBitmap>;
|
|
209
|
+
/** Clean up all WebGL resources. */
|
|
210
|
+
destroy(): void;
|
|
211
|
+
/** Returns the WebGL2 context for external rendering (e.g. CRT shader). */
|
|
212
|
+
getGL(): WebGL2RenderingContext;
|
|
213
|
+
/** Returns the ready FBO's texture — the latest fully rendered frame. */
|
|
214
|
+
getReadyTexture(): WebGLTexture;
|
|
215
|
+
private _displayLoop;
|
|
216
|
+
/**
|
|
217
|
+
* Render the ready FBO to the display canvas via passthrough shader.
|
|
218
|
+
* Called at vsync rate by RAF, or once synchronously for screenshots.
|
|
219
|
+
*/
|
|
220
|
+
private _renderDisplay;
|
|
221
|
+
private _initFBOs;
|
|
222
|
+
private _createFBO;
|
|
223
|
+
private _clearAllFBOs;
|
|
224
|
+
private _initQuadVBO;
|
|
225
|
+
private _createShaderProgram;
|
|
226
|
+
private _compileShader;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { }
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "canvas-ultrafast",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "WebGL-accelerated Canvas 2D rendering engine with triple-buffered FBOs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/canvas-ultrafast.0a27ed3f.umd.js",
|
|
7
|
+
"module": "dist/canvas-ultrafast.0a27ed3f.es.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/canvas-ultrafast.es.js",
|
|
13
|
+
"require": "./dist/canvas-ultrafast.umd.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"workspaces": [
|
|
17
|
+
"demo"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"type-check": "tsc --noEmit && npm run lint",
|
|
22
|
+
"build": "npm run type-check && NODE_ENV=production vite build --mode production",
|
|
23
|
+
"build:dev": "npm run type-check && NODE_ENV=development vite build --mode development",
|
|
24
|
+
"build:all": "npm run build && npm run build -w demo",
|
|
25
|
+
"build:dev-all": "npm run build:dev && npm run build:dev -w demo",
|
|
26
|
+
"clean": "rm -rf dist demo/dist",
|
|
27
|
+
"serve": "node scripts/server.js demo/dist 4174",
|
|
28
|
+
"serve:demo": "npm run build:all && node scripts/server.js demo/dist 4174",
|
|
29
|
+
"verify-demo": "npm run build:dev-all && node scripts/verify-demo.js"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"canvas",
|
|
33
|
+
"webgl",
|
|
34
|
+
"renderer",
|
|
35
|
+
"triple-buffer",
|
|
36
|
+
"typescript"
|
|
37
|
+
],
|
|
38
|
+
"license": "AGPL-3.0-only",
|
|
39
|
+
"dependencies": {},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist"
|
|
42
|
+
],
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@eslint/js": "^10.0.1",
|
|
45
|
+
"@microsoft/api-extractor": "^7.57.2",
|
|
46
|
+
"@types/node": "^22.19.11",
|
|
47
|
+
"eslint": "^10.0.1",
|
|
48
|
+
"globals": "^16.0.0",
|
|
49
|
+
"playwright": "^1.58.2",
|
|
50
|
+
"typescript": "~5.8.2",
|
|
51
|
+
"typescript-eslint": "^8.56.0",
|
|
52
|
+
"vite": "^7.3.1",
|
|
53
|
+
"vite-plugin-compression2": "^2.4.0",
|
|
54
|
+
"vite-plugin-dts": "^4.5.4"
|
|
55
|
+
}
|
|
56
|
+
}
|