|
| 1 | +import { describe, it, expect, vi } from 'vitest'; |
| 2 | +import { uploadCompressedTexture } from './textureCompression.js'; |
| 3 | +import type { WebGlContextWrapper } from './WebGlContextWrapper.js'; |
| 4 | +import type { CompressedData } from '../textures/Texture.js'; |
| 5 | + |
| 6 | +/** |
| 7 | + * A compressed format enum is only valid in compressedTexImage2D after its |
| 8 | + * owning extension has been enabled via getExtension; otherwise the driver |
| 9 | + * rejects it with GL_INVALID_ENUM (1280). These tests pin that every upload |
| 10 | + * path enables its extension *before* uploading, and fails loudly when the |
| 11 | + * device exposes none of the candidates. |
| 12 | + */ |
| 13 | + |
| 14 | +interface MockGlw { |
| 15 | + glw: WebGlContextWrapper; |
| 16 | + order: string[]; |
| 17 | + getExtension: ReturnType<typeof vi.fn>; |
| 18 | + compressedTexImage2D: ReturnType<typeof vi.fn>; |
| 19 | +} |
| 20 | + |
| 21 | +function makeGlw(supported: Set<string>): MockGlw { |
| 22 | + const order: string[] = []; |
| 23 | + const getExtension = vi.fn((name: string) => { |
| 24 | + order.push(`getExtension:${name}`); |
| 25 | + return supported.has(name) === true ? {} : null; |
| 26 | + }); |
| 27 | + const compressedTexImage2D = vi.fn(() => { |
| 28 | + order.push('compressedTexImage2D'); |
| 29 | + }); |
| 30 | + const glw = { |
| 31 | + getExtension, |
| 32 | + compressedTexImage2D, |
| 33 | + bindTexture: vi.fn(), |
| 34 | + texParameteri: vi.fn(), |
| 35 | + TEXTURE_WRAP_S: 0, |
| 36 | + TEXTURE_WRAP_T: 0, |
| 37 | + TEXTURE_MAG_FILTER: 0, |
| 38 | + TEXTURE_MIN_FILTER: 0, |
| 39 | + CLAMP_TO_EDGE: 0, |
| 40 | + LINEAR: 0, |
| 41 | + LINEAR_MIPMAP_LINEAR: 0, |
| 42 | + } as unknown as WebGlContextWrapper; |
| 43 | + return { glw, order, getExtension, compressedTexImage2D }; |
| 44 | +} |
| 45 | + |
| 46 | +function makeData( |
| 47 | + type: 'ktx' | 'pvr' | 'astc', |
| 48 | + glInternalFormat: number, |
| 49 | +): CompressedData { |
| 50 | + return { |
| 51 | + type, |
| 52 | + glInternalFormat, |
| 53 | + w: 4, |
| 54 | + h: 4, |
| 55 | + mipmaps: [new ArrayBuffer(16)], |
| 56 | + blockInfo: { width: 4, height: 4, bytes: 16 }, |
| 57 | + }; |
| 58 | +} |
| 59 | + |
| 60 | +const texture = {} as WebGLTexture; |
| 61 | + |
| 62 | +// COMPRESSED_RGBA_S3TC_DXT5_EXT |
| 63 | +const S3TC_DXT5 = 0x83f3; |
| 64 | +// COMPRESSED_RGB_PVRTC_4BPPV1_IMG |
| 65 | +const PVRTC_4BPP = 0x8c00; |
| 66 | +// COMPRESSED_RGB_ETC1_WEBGL |
| 67 | +const ETC1 = 0x8d64; |
| 68 | +// COMPRESSED_RGBA_ASTC_4x4_KHR |
| 69 | +const ASTC_4x4 = 0x93b0; |
| 70 | + |
| 71 | +describe('compressed texture extension guards', () => { |
| 72 | + it('KTX enables the s3tc extension before uploading', () => { |
| 73 | + const m = makeGlw(new Set(['WEBGL_compressed_texture_s3tc'])); |
| 74 | + uploadCompressedTexture.ktx!(m.glw, texture, makeData('ktx', S3TC_DXT5)); |
| 75 | + |
| 76 | + expect(m.getExtension).toHaveBeenCalledWith( |
| 77 | + 'WEBGL_compressed_texture_s3tc', |
| 78 | + ); |
| 79 | + expect(m.compressedTexImage2D).toHaveBeenCalled(); |
| 80 | + // getExtension must precede the first compressedTexImage2D call |
| 81 | + expect(m.order.indexOf('getExtension:WEBGL_compressed_texture_s3tc')).toBe( |
| 82 | + 0, |
| 83 | + ); |
| 84 | + expect( |
| 85 | + m.order.indexOf('getExtension:WEBGL_compressed_texture_s3tc') < |
| 86 | + m.order.indexOf('compressedTexImage2D'), |
| 87 | + ).toBe(true); |
| 88 | + }); |
| 89 | + |
| 90 | + it('KTX throws (no silent 1280) when the s3tc extension is unavailable', () => { |
| 91 | + const m = makeGlw(new Set()); |
| 92 | + expect(() => |
| 93 | + uploadCompressedTexture.ktx!(m.glw, texture, makeData('ktx', S3TC_DXT5)), |
| 94 | + ).toThrow(/not supported/); |
| 95 | + expect(m.compressedTexImage2D).not.toHaveBeenCalled(); |
| 96 | + }); |
| 97 | + |
| 98 | + it('KTX enables the etc1 extension for ETC1 formats', () => { |
| 99 | + const m = makeGlw(new Set(['WEBGL_compressed_texture_etc1'])); |
| 100 | + uploadCompressedTexture.ktx!(m.glw, texture, makeData('ktx', ETC1)); |
| 101 | + expect(m.getExtension).toHaveBeenCalledWith( |
| 102 | + 'WEBGL_compressed_texture_etc1', |
| 103 | + ); |
| 104 | + expect(m.compressedTexImage2D).toHaveBeenCalled(); |
| 105 | + }); |
| 106 | + |
| 107 | + it('PVR enables the pvrtc extension before uploading', () => { |
| 108 | + const m = makeGlw(new Set(['WEBGL_compressed_texture_pvrtc'])); |
| 109 | + uploadCompressedTexture.pvr!(m.glw, texture, makeData('pvr', PVRTC_4BPP)); |
| 110 | + expect(m.getExtension).toHaveBeenCalledWith( |
| 111 | + 'WEBGL_compressed_texture_pvrtc', |
| 112 | + ); |
| 113 | + expect( |
| 114 | + m.order.indexOf('getExtension:WEBGL_compressed_texture_pvrtc') < |
| 115 | + m.order.indexOf('compressedTexImage2D'), |
| 116 | + ).toBe(true); |
| 117 | + }); |
| 118 | + |
| 119 | + it('PVR falls back to the WebKit-prefixed pvrtc extension', () => { |
| 120 | + const m = makeGlw(new Set(['WEBKIT_WEBGL_compressed_texture_pvrtc'])); |
| 121 | + uploadCompressedTexture.pvr!(m.glw, texture, makeData('pvr', PVRTC_4BPP)); |
| 122 | + expect(m.getExtension).toHaveBeenCalledWith( |
| 123 | + 'WEBGL_compressed_texture_pvrtc', |
| 124 | + ); |
| 125 | + expect(m.getExtension).toHaveBeenCalledWith( |
| 126 | + 'WEBKIT_WEBGL_compressed_texture_pvrtc', |
| 127 | + ); |
| 128 | + expect(m.compressedTexImage2D).toHaveBeenCalled(); |
| 129 | + }); |
| 130 | + |
| 131 | + it('PVR throws when neither pvrtc extension is available', () => { |
| 132 | + const m = makeGlw(new Set()); |
| 133 | + expect(() => |
| 134 | + uploadCompressedTexture.pvr!(m.glw, texture, makeData('pvr', PVRTC_4BPP)), |
| 135 | + ).toThrow(/not supported/); |
| 136 | + expect(m.compressedTexImage2D).not.toHaveBeenCalled(); |
| 137 | + }); |
| 138 | + |
| 139 | + it('ASTC enables the astc extension before uploading', () => { |
| 140 | + const m = makeGlw(new Set(['WEBGL_compressed_texture_astc'])); |
| 141 | + uploadCompressedTexture.astc!(m.glw, texture, makeData('astc', ASTC_4x4)); |
| 142 | + expect(m.getExtension).toHaveBeenCalledWith( |
| 143 | + 'WEBGL_compressed_texture_astc', |
| 144 | + ); |
| 145 | + expect( |
| 146 | + m.order.indexOf('getExtension:WEBGL_compressed_texture_astc') < |
| 147 | + m.order.indexOf('compressedTexImage2D'), |
| 148 | + ).toBe(true); |
| 149 | + }); |
| 150 | + |
| 151 | + it('ASTC throws when the astc extension is unavailable', () => { |
| 152 | + const m = makeGlw(new Set()); |
| 153 | + expect(() => |
| 154 | + uploadCompressedTexture.astc!(m.glw, texture, makeData('astc', ASTC_4x4)), |
| 155 | + ).toThrow(/not supported/); |
| 156 | + expect(m.compressedTexImage2D).not.toHaveBeenCalled(); |
| 157 | + }); |
| 158 | +}); |
0 commit comments