diff --git a/BROWSERS.md b/BROWSERS.md index 9afe3051..3fc08499 100644 --- a/BROWSERS.md +++ b/BROWSERS.md @@ -56,13 +56,13 @@ Add the following to your \`vite.config.js\`: import legacy from '@vitejs/plugin-legacy'; export default { -plugins: [ -legacy({ -targets: ['chrome>=38'], -modernPolyfills: true, -additionalLegacyPolyfills: ['whatwg-fetch'], -}), -], + plugins: [ + legacy({ + targets: ['chrome>=38'], + modernPolyfills: true, + additionalLegacyPolyfills: ['whatwg-fetch'], + }), + ], }; ``` diff --git a/src/core/lib/WebGlContextWrapper.ts b/src/core/lib/WebGlContextWrapper.ts index f87f7e3c..dd1e00e5 100644 --- a/src/core/lib/WebGlContextWrapper.ts +++ b/src/core/lib/WebGlContextWrapper.ts @@ -84,6 +84,8 @@ export class WebGlContextWrapper { public readonly LINK_STATUS; public readonly DYNAMIC_DRAW; public readonly COLOR_ATTACHMENT0; + public readonly INVALID_ENUM: number; + public readonly INVALID_OPERATION: number; //#endregion WebGL Enums constructor(private gl: WebGLRenderingContext | WebGL2RenderingContext) { @@ -175,6 +177,8 @@ export class WebGlContextWrapper { this.LINK_STATUS = gl.LINK_STATUS; this.DYNAMIC_DRAW = gl.DYNAMIC_DRAW; this.COLOR_ATTACHMENT0 = gl.COLOR_ATTACHMENT0; + this.INVALID_ENUM = gl.INVALID_ENUM; + this.INVALID_OPERATION = gl.INVALID_OPERATION; } /** * Returns true if the WebGL context is WebGL2 @@ -1020,6 +1024,18 @@ export class WebGlContextWrapper { return gl.getExtension(name); } + /** + * ``` + * gl.getError(type); + * ``` + * + * @returns + */ + getError() { + const { gl } = this; + return gl.getError(); + } + /** * ``` * gl.createVertexArray(); diff --git a/src/core/renderers/canvas/CanvasCoreRenderer.ts b/src/core/renderers/canvas/CanvasCoreRenderer.ts index d4c53ab6..d5bb799f 100644 --- a/src/core/renderers/canvas/CanvasCoreRenderer.ts +++ b/src/core/renderers/canvas/CanvasCoreRenderer.ts @@ -30,7 +30,12 @@ import { type QuadOptions, } from '../CoreRenderer.js'; import { CanvasCoreTexture } from './CanvasCoreTexture.js'; -import { getBorder, getRadius, strokeLine } from './internal/C2DShaderUtils.js'; +import { + getBorder, + getRadius, + roundRect, + strokeLine, +} from './internal/C2DShaderUtils.js'; import { formatRgba, parseColorRgba, @@ -166,7 +171,7 @@ export class CanvasCoreRenderer extends CoreRenderer { if (radius) { const path = new Path2D(); - path.roundRect(tx, ty, width, height, radius); + roundRect.call(path, tx, ty, width, height, radius); ctx.clip(path); } @@ -224,7 +229,8 @@ export class CanvasCoreRenderer extends CoreRenderer { ctx.strokeStyle = borderColor; ctx.globalAlpha = alpha; if (radius) { - ctx.roundRect( + roundRect.call( + ctx, tx + borderInnerWidth, ty + borderInnerWidth, width - borderWidth, diff --git a/src/core/renderers/canvas/internal/C2DShaderUtils.ts b/src/core/renderers/canvas/internal/C2DShaderUtils.ts index 52fb7586..85446dda 100644 --- a/src/core/renderers/canvas/internal/C2DShaderUtils.ts +++ b/src/core/renderers/canvas/internal/C2DShaderUtils.ts @@ -84,6 +84,99 @@ export function getBorder( return undefined; } +export function roundRect( + this: CanvasRenderingContext2D | Path2D, + x: number, + y: number, + width: number, + height: number, + radius: number | DOMPointInit | (number | DOMPointInit)[], +) { + const context = Object.getPrototypeOf(this) as Path2D; + if (!context.roundRect) { + const fixOverlappingCorners = (radii: { + topLeft: number; + topRight: number; + bottomRight: number; + bottomLeft: number; + }) => { + const maxRadius = Math.min(width / 2, height / 2); + const totalHorizontal = + radii.topLeft + radii.topRight + radii.bottomRight + radii.bottomLeft; + + if (totalHorizontal > width || totalHorizontal > height) { + const scale = + maxRadius / + Math.max( + radii.topLeft, + radii.topRight, + radii.bottomRight, + radii.bottomLeft, + ); + radii.topLeft *= scale; + radii.topRight *= scale; + radii.bottomRight *= scale; + radii.bottomLeft *= scale; + } + }; + const radii = + typeof radius === 'number' + ? { + topLeft: radius, + topRight: radius, + bottomRight: radius, + bottomLeft: radius, + } + : { topLeft: 0, topRight: 0, bottomRight: 0, bottomLeft: 0, ...radius }; + + fixOverlappingCorners(radii); + + this.moveTo(x + radii.topLeft, y); + this.lineTo(x + width - radii.topRight, y); + this.ellipse( + x + width - radii.topRight, + y + radii.topRight, + radii.topRight, + radii.topRight, + 0, + 1.5 * Math.PI, + 2 * Math.PI, + ); + this.lineTo(x + width, y + height - radii.bottomRight); + this.ellipse( + x + width - radii.bottomRight, + y + height - radii.bottomRight, + radii.bottomRight, + radii.bottomRight, + 0, + 0, + 0.5 * Math.PI, + ); + this.lineTo(x + radii.bottomLeft, y + height); + this.ellipse( + x + radii.bottomLeft, + y + height - radii.bottomLeft, + radii.bottomLeft, + radii.bottomLeft, + 0, + 0.5 * Math.PI, + Math.PI, + ); + this.lineTo(x, y + radii.topLeft); + this.ellipse( + x + radii.topLeft, + y + radii.topLeft, + radii.topLeft, + radii.topLeft, + 0, + Math.PI, + 1.5 * Math.PI, + ); + } else { + this.roundRect(x, y, width, height, radius); + } +} + export function strokeLine( ctx: CanvasRenderingContext2D, x: number, diff --git a/src/core/renderers/webgl/WebGlCoreShader.ts b/src/core/renderers/webgl/WebGlCoreShader.ts index 323f1dd1..0345833d 100644 --- a/src/core/renderers/webgl/WebGlCoreShader.ts +++ b/src/core/renderers/webgl/WebGlCoreShader.ts @@ -142,9 +142,15 @@ export abstract class WebGlCoreShader extends CoreShader { glw.FRAGMENT_SHADER, fragmentSource, ); + if (!vertexShader || !fragmentShader) { throw new Error( - `Unable to create shader type: ${glw.FRAGMENT_SHADER}. Source: ${fragmentSource}`, + `Unable to create the following shader(s): ${[ + !vertexShader && 'VERTEX_SHADER', + !fragmentShader && 'FRAGMENT_SHADER', + ] + .filter(Boolean) + .join(' and ')}`, ); } diff --git a/src/core/renderers/webgl/internal/ShaderUtils.ts b/src/core/renderers/webgl/internal/ShaderUtils.ts index 1c682bca..09181b22 100644 --- a/src/core/renderers/webgl/internal/ShaderUtils.ts +++ b/src/core/renderers/webgl/internal/ShaderUtils.ts @@ -100,8 +100,14 @@ export function createShader( ) { const shader = glw.createShader(type); if (!shader) { - throw new Error(`Unable to create shader type: ${type}. Source: ${source}`); + const glError = glw.getError(); + throw new Error( + `Unable to create the shader: ${ + type === glw.VERTEX_SHADER ? 'VERTEX_SHADER' : 'FRAGMENT_SHADER' + }.${glError ? ` WebGlContext Error: ${glError}` : ''}`, + ); } + glw.shaderSource(shader, source); glw.compileShader(shader); const success = !!glw.getShaderParameter(shader, glw.COMPILE_STATUS); @@ -109,7 +115,7 @@ export function createShader( return shader; } - console.log(glw.getShaderInfoLog(shader)); + console.error(glw.getShaderInfoLog(shader)); glw.deleteShader(shader); } @@ -131,7 +137,7 @@ export function createProgram( return program; } - console.log(glw.getProgramInfoLog(program)); + console.warn(glw.getProgramInfoLog(program)); glw.deleteProgram(program); return undefined; } diff --git a/visual-regression/src/snapshot.ts b/visual-regression/src/snapshot.ts index 4c2355f4..877744a4 100644 --- a/visual-regression/src/snapshot.ts +++ b/visual-regression/src/snapshot.ts @@ -149,7 +149,7 @@ export async function saveFailedSnapshot( writeTasks.push( fs.promises.writeFile( path.join(failedResultsDir, `${subtestName}-${snapshotIndex}-diff.png`), - + PNG.sync.write(diffPng), ), ); @@ -171,7 +171,6 @@ export function compareBuffers( width: number, height: number, ): CompareResult { - const diff = new PNG({ width: width as number, height: height as number }); const actualImage = PNG.sync.read(actualImageBuffer); const expectedImage = PNG.sync.read(expectedImageBuffer); @@ -187,11 +186,10 @@ export function compareBuffers( }; } - const count = pixelmatch( actualImage.data, expectedImage.data, - + diff.data, width, height, @@ -202,7 +200,7 @@ export function compareBuffers( return { doesMatch, - + diffImageBuffer: doesMatch ? undefined : diff, reason: doesMatch ? undefined : `${count} pixels differ`, };