@bloopjs/toodle 0.0.103 → 0.1.1

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.
Files changed (128) hide show
  1. package/dist/Toodle.d.ts +50 -20
  2. package/dist/Toodle.d.ts.map +1 -1
  3. package/dist/backends/IBackendShader.d.ts +48 -0
  4. package/dist/backends/IBackendShader.d.ts.map +1 -0
  5. package/dist/backends/IRenderBackend.d.ts +92 -0
  6. package/dist/backends/IRenderBackend.d.ts.map +1 -0
  7. package/dist/backends/ITextureAtlas.d.ts +34 -0
  8. package/dist/backends/ITextureAtlas.d.ts.map +1 -0
  9. package/dist/backends/detection.d.ts +16 -0
  10. package/dist/backends/detection.d.ts.map +1 -0
  11. package/dist/backends/mod.d.ts +9 -0
  12. package/dist/backends/mod.d.ts.map +1 -0
  13. package/dist/backends/webgl2/WebGLBackend.d.ts +51 -0
  14. package/dist/backends/webgl2/WebGLBackend.d.ts.map +1 -0
  15. package/dist/backends/webgl2/WebGLQuadShader.d.ts +17 -0
  16. package/dist/backends/webgl2/WebGLQuadShader.d.ts.map +1 -0
  17. package/dist/backends/webgl2/glsl/quad.glsl.d.ts +12 -0
  18. package/dist/backends/webgl2/glsl/quad.glsl.d.ts.map +1 -0
  19. package/dist/backends/webgl2/mod.d.ts +3 -0
  20. package/dist/backends/webgl2/mod.d.ts.map +1 -0
  21. package/dist/backends/webgpu/ShaderDescriptor.d.ts.map +1 -0
  22. package/dist/{textures → backends/webgpu}/TextureComputeShader.d.ts +1 -1
  23. package/dist/backends/webgpu/TextureComputeShader.d.ts.map +1 -0
  24. package/dist/backends/webgpu/WebGPUBackend.d.ts +67 -0
  25. package/dist/backends/webgpu/WebGPUBackend.d.ts.map +1 -0
  26. package/dist/backends/webgpu/WebGPUQuadShader.d.ts +18 -0
  27. package/dist/backends/webgpu/WebGPUQuadShader.d.ts.map +1 -0
  28. package/dist/backends/webgpu/mod.d.ts +3 -0
  29. package/dist/backends/webgpu/mod.d.ts.map +1 -0
  30. package/dist/backends/webgpu/parser.d.ts.map +1 -0
  31. package/dist/{shaders → backends/webgpu}/postprocess/blur.d.ts +1 -1
  32. package/dist/backends/webgpu/postprocess/blur.d.ts.map +1 -0
  33. package/dist/{shaders → backends/webgpu}/postprocess/mod.d.ts +1 -1
  34. package/dist/backends/webgpu/postprocess/mod.d.ts.map +1 -0
  35. package/dist/backends/webgpu/samplers.d.ts.map +1 -0
  36. package/dist/backends/webgpu/wgsl/example.wgsl.d.ts.map +1 -0
  37. package/dist/backends/webgpu/wgsl/hello.wgsl.d.ts.map +1 -0
  38. package/dist/backends/webgpu/wgsl/helloInstanced.wgsl.d.ts.map +1 -0
  39. package/dist/backends/webgpu/wgsl/pixel-scraping.wgsl.d.ts.map +1 -0
  40. package/dist/backends/webgpu/wgsl/quad.wgsl.d.ts.map +1 -0
  41. package/dist/coreTypes/EngineUniform.d.ts.map +1 -0
  42. package/dist/mod.d.ts +3 -2
  43. package/dist/mod.d.ts.map +1 -1
  44. package/dist/mod.js +6741 -6151
  45. package/dist/mod.js.map +28 -23
  46. package/dist/scene/Batcher.d.ts +2 -2
  47. package/dist/scene/Batcher.d.ts.map +1 -1
  48. package/dist/scene/QuadNode.d.ts +3 -2
  49. package/dist/scene/QuadNode.d.ts.map +1 -1
  50. package/dist/scene/RenderComponent.d.ts +2 -2
  51. package/dist/scene/RenderComponent.d.ts.map +1 -1
  52. package/dist/scene/SceneNode.d.ts +2 -2
  53. package/dist/scene/SceneNode.d.ts.map +1 -1
  54. package/dist/text/TextShader.d.ts +8 -6
  55. package/dist/text/TextShader.d.ts.map +1 -1
  56. package/dist/textures/AssetManager.d.ts +21 -5
  57. package/dist/textures/AssetManager.d.ts.map +1 -1
  58. package/dist/textures/types.d.ts +4 -2
  59. package/dist/textures/types.d.ts.map +1 -1
  60. package/dist/textures/util.d.ts.map +1 -1
  61. package/dist/utils/boilerplate.d.ts +1 -1
  62. package/dist/utils/boilerplate.d.ts.map +1 -1
  63. package/package.json +1 -1
  64. package/src/Toodle.ts +155 -171
  65. package/src/backends/IBackendShader.ts +52 -0
  66. package/src/backends/IRenderBackend.ts +118 -0
  67. package/src/backends/ITextureAtlas.ts +35 -0
  68. package/src/backends/detection.ts +46 -0
  69. package/src/backends/mod.ts +29 -0
  70. package/src/backends/webgl2/WebGLBackend.ts +256 -0
  71. package/src/backends/webgl2/WebGLQuadShader.ts +278 -0
  72. package/src/backends/webgl2/glsl/quad.glsl.ts +114 -0
  73. package/src/backends/webgl2/mod.ts +2 -0
  74. package/src/{textures → backends/webgpu}/TextureComputeShader.ts +2 -48
  75. package/src/backends/webgpu/WebGPUBackend.ts +350 -0
  76. package/src/{shaders/QuadShader.ts → backends/webgpu/WebGPUQuadShader.ts} +226 -170
  77. package/src/backends/webgpu/mod.ts +2 -0
  78. package/src/{shaders → backends/webgpu}/parser.ts +2 -2
  79. package/src/{shaders → backends/webgpu}/postprocess/blur.ts +2 -2
  80. package/src/{shaders → backends/webgpu}/postprocess/mod.ts +1 -1
  81. package/src/mod.ts +3 -2
  82. package/src/scene/Batcher.ts +3 -3
  83. package/src/scene/QuadNode.ts +7 -6
  84. package/src/scene/RenderComponent.ts +2 -2
  85. package/src/scene/SceneNode.ts +11 -12
  86. package/src/text/TextNode.ts +2 -2
  87. package/src/text/TextShader.ts +17 -11
  88. package/src/textures/AssetManager.ts +119 -94
  89. package/src/textures/types.ts +4 -2
  90. package/src/textures/util.ts +0 -65
  91. package/src/utils/boilerplate.ts +1 -1
  92. package/dist/shaders/EngineUniform.d.ts.map +0 -1
  93. package/dist/shaders/IShader.d.ts +0 -15
  94. package/dist/shaders/IShader.d.ts.map +0 -1
  95. package/dist/shaders/QuadShader.d.ts +0 -18
  96. package/dist/shaders/QuadShader.d.ts.map +0 -1
  97. package/dist/shaders/ShaderDescriptor.d.ts.map +0 -1
  98. package/dist/shaders/mod.d.ts +0 -6
  99. package/dist/shaders/mod.d.ts.map +0 -1
  100. package/dist/shaders/parser.d.ts.map +0 -1
  101. package/dist/shaders/postprocess/blur.d.ts.map +0 -1
  102. package/dist/shaders/postprocess/mod.d.ts.map +0 -1
  103. package/dist/shaders/samplers.d.ts.map +0 -1
  104. package/dist/shaders/wgsl/example.wgsl.d.ts.map +0 -1
  105. package/dist/shaders/wgsl/hello.wgsl.d.ts.map +0 -1
  106. package/dist/shaders/wgsl/helloInstanced.wgsl.d.ts.map +0 -1
  107. package/dist/shaders/wgsl/quad.wgsl.d.ts.map +0 -1
  108. package/dist/textures/TextureComputeShader.d.ts.map +0 -1
  109. package/dist/textures/pixel-scraping.wgsl.d.ts.map +0 -1
  110. package/src/shaders/IShader.ts +0 -20
  111. package/src/shaders/mod.ts +0 -5
  112. /package/dist/{shaders → backends/webgpu}/ShaderDescriptor.d.ts +0 -0
  113. /package/dist/{shaders → backends/webgpu}/parser.d.ts +0 -0
  114. /package/dist/{shaders → backends/webgpu}/samplers.d.ts +0 -0
  115. /package/dist/{shaders → backends/webgpu}/wgsl/example.wgsl.d.ts +0 -0
  116. /package/dist/{shaders → backends/webgpu}/wgsl/hello.wgsl.d.ts +0 -0
  117. /package/dist/{shaders → backends/webgpu}/wgsl/helloInstanced.wgsl.d.ts +0 -0
  118. /package/dist/{textures → backends/webgpu/wgsl}/pixel-scraping.wgsl.d.ts +0 -0
  119. /package/dist/{shaders → backends/webgpu}/wgsl/quad.wgsl.d.ts +0 -0
  120. /package/dist/{shaders → coreTypes}/EngineUniform.d.ts +0 -0
  121. /package/src/{shaders → backends/webgpu}/ShaderDescriptor.ts +0 -0
  122. /package/src/{shaders → backends/webgpu}/samplers.ts +0 -0
  123. /package/src/{shaders → backends/webgpu}/wgsl/example.wgsl.ts +0 -0
  124. /package/src/{shaders → backends/webgpu}/wgsl/hello.wgsl.ts +0 -0
  125. /package/src/{shaders → backends/webgpu}/wgsl/helloInstanced.wgsl.ts +0 -0
  126. /package/src/{textures → backends/webgpu/wgsl}/pixel-scraping.wgsl.ts +0 -0
  127. /package/src/{shaders → backends/webgpu}/wgsl/quad.wgsl.ts +0 -0
  128. /package/src/{shaders → coreTypes}/EngineUniform.ts +0 -0
@@ -0,0 +1,118 @@
1
+ import type { Color } from "../coreTypes/Color";
2
+ import type { EngineUniform } from "../coreTypes/EngineUniform";
3
+ import type { Size } from "../coreTypes/Size";
4
+ import type { Limits } from "../limits";
5
+ import type { CpuTextureAtlas } from "../textures/types";
6
+ import type { IBackendShader, QuadShaderCreationOpts } from "./IBackendShader";
7
+ import type { ITextureAtlas, TextureAtlasOptions } from "./ITextureAtlas";
8
+
9
+ export type BackendType = "webgpu" | "webgl2";
10
+
11
+ export type BlendFactor =
12
+ | "one"
13
+ | "zero"
14
+ | "src-alpha"
15
+ | "one-minus-src-alpha"
16
+ | "dst-alpha"
17
+ | "one-minus-dst-alpha";
18
+ export type BlendOperation = "add" | "subtract" | "reverse-subtract";
19
+
20
+ export type BlendMode = {
21
+ color: {
22
+ srcFactor: BlendFactor;
23
+ dstFactor: BlendFactor;
24
+ operation: BlendOperation;
25
+ };
26
+ alpha: {
27
+ srcFactor: BlendFactor;
28
+ dstFactor: BlendFactor;
29
+ operation: BlendOperation;
30
+ };
31
+ };
32
+
33
+ /**
34
+ * The render backend interface abstracts WebGPU and WebGL differences.
35
+ *
36
+ * Implementations handle GPU-specific operations like texture management,
37
+ * shader creation, and frame lifecycle.
38
+ */
39
+ export interface IRenderBackend {
40
+ /** The type of backend ("webgpu" or "webgl2") */
41
+ readonly type: BackendType;
42
+
43
+ /** Engine limits (texture size, instance count, etc.) */
44
+ readonly limits: Limits;
45
+
46
+ /** Size of the default texture atlas */
47
+ readonly atlasSize: Size;
48
+
49
+ /** Default atlas ID (always "default") */
50
+ readonly defaultAtlasId: string;
51
+
52
+ /**
53
+ * Begin a new frame.
54
+ * WebGPU: Creates command encoder and render pass
55
+ * WebGL: Clears the canvas if loadOp is "clear"
56
+ */
57
+ startFrame(clearColor: Color, loadOp: "clear" | "load"): void;
58
+
59
+ /**
60
+ * End the current frame and submit to GPU.
61
+ * WebGPU: Ends render pass and submits command buffer
62
+ * WebGL: Flushes pending operations
63
+ */
64
+ endFrame(): void;
65
+
66
+ /**
67
+ * Update engine uniforms (view-projection matrix, resolution).
68
+ * Called once per frame before shader processing.
69
+ */
70
+ updateEngineUniform(uniform: EngineUniform): void;
71
+
72
+ /**
73
+ * Upload a CPU texture atlas to a GPU texture array layer.
74
+ * @param atlas - The CPU-side atlas data to upload
75
+ * @param layerIndex - Which layer in the texture array to upload to
76
+ * @param atlasId - Which atlas to upload to (default: "default")
77
+ */
78
+ uploadAtlas(
79
+ atlas: CpuTextureAtlas,
80
+ layerIndex: number,
81
+ atlasId?: string,
82
+ ): Promise<void>;
83
+
84
+ /**
85
+ * Create a new texture atlas.
86
+ * @param id - Unique identifier for this atlas
87
+ * @param options - Atlas configuration (format, layers, size)
88
+ */
89
+ createTextureAtlas(id: string, options?: TextureAtlasOptions): ITextureAtlas;
90
+
91
+ /**
92
+ * Get a texture atlas by ID.
93
+ * @param id - Atlas identifier or defaults to "default"
94
+ * @returns The atlas, or null if not found
95
+ */
96
+ getTextureAtlas(id?: string): ITextureAtlas | null;
97
+
98
+ /**
99
+ * Destroy a texture atlas and free GPU resources.
100
+ * @param id - Atlas identifier
101
+ */
102
+ destroyTextureAtlas(id: string): void;
103
+
104
+ /**
105
+ * Create a quad shader for instanced rendering.
106
+ */
107
+ createQuadShader(opts: QuadShaderCreationOpts): IBackendShader;
108
+
109
+ /**
110
+ * Handle canvas resize.
111
+ */
112
+ resize(width: number, height: number): void;
113
+
114
+ /**
115
+ * Clean up GPU resources.
116
+ */
117
+ destroy(): void;
118
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Supported texture atlas formats.
3
+ */
4
+ export type TextureAtlasFormat = "rgba8unorm" | "rg8unorm";
5
+
6
+ /**
7
+ * Options for creating a texture atlas.
8
+ */
9
+ export type TextureAtlasOptions = {
10
+ /** Texture format (default: "rgba8unorm") */
11
+ format?: TextureAtlasFormat;
12
+ /** Number of array layers (default: limits.textureArrayLayers) */
13
+ layers?: number;
14
+ /** Atlas size in pixels (default: limits.textureSize) */
15
+ size?: number;
16
+ };
17
+
18
+ /**
19
+ * Backend-agnostic texture atlas interface.
20
+ *
21
+ * Texture atlases are GPU texture arrays that store multiple textures.
22
+ * Each backend manages its atlases and provides this common interface.
23
+ */
24
+ export interface ITextureAtlas {
25
+ /** Unique identifier for this atlas */
26
+ readonly id: string;
27
+ /** Texture format */
28
+ readonly format: TextureAtlasFormat;
29
+ /** Number of array layers */
30
+ readonly layers: number;
31
+ /** Size in pixels (width = height) */
32
+ readonly size: number;
33
+ /** Underlying GPU handle (GPUTexture for WebGPU, WebGLTexture for WebGL) */
34
+ readonly handle: unknown;
35
+ }
@@ -0,0 +1,46 @@
1
+ import type { BackendType } from "./IRenderBackend";
2
+
3
+ /**
4
+ * Detect the best available rendering backend.
5
+ *
6
+ * Tries WebGPU first (better performance), falls back to WebGL 2.
7
+ */
8
+ export async function detectBackend(): Promise<BackendType> {
9
+ // Check for WebGPU support
10
+ if (typeof navigator !== "undefined" && "gpu" in navigator) {
11
+ try {
12
+ const adapter = await navigator.gpu.requestAdapter();
13
+ if (adapter) {
14
+ return "webgpu";
15
+ }
16
+ } catch {
17
+ // WebGPU initialization failed, fall back to WebGL
18
+ }
19
+ }
20
+
21
+ return "webgl2";
22
+ }
23
+
24
+ /**
25
+ * Check if WebGPU is available in the current environment.
26
+ */
27
+ export function isWebGPUAvailable(): boolean {
28
+ return typeof navigator !== "undefined" && "gpu" in navigator;
29
+ }
30
+
31
+ /**
32
+ * Check if WebGL 2 is available in the current environment.
33
+ */
34
+ export function isWebGL2Available(): boolean {
35
+ if (typeof document === "undefined") {
36
+ return false;
37
+ }
38
+
39
+ try {
40
+ const canvas = document.createElement("canvas");
41
+ const gl = canvas.getContext("webgl2");
42
+ return gl !== null;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
@@ -0,0 +1,29 @@
1
+ // Backend abstraction layer
2
+
3
+ export {
4
+ detectBackend,
5
+ isWebGL2Available,
6
+ isWebGPUAvailable,
7
+ } from "./detection";
8
+ export type { IBackendShader, QuadShaderCreationOpts } from "./IBackendShader";
9
+ export type {
10
+ BackendType,
11
+ BlendFactor,
12
+ BlendMode,
13
+ BlendOperation,
14
+ IRenderBackend,
15
+ } from "./IRenderBackend";
16
+ export type {
17
+ ITextureAtlas,
18
+ TextureAtlasFormat,
19
+ TextureAtlasOptions,
20
+ } from "./ITextureAtlas";
21
+ export { defaultFragmentShader as defaultGLSLFragmentShader } from "./webgl2/glsl/quad.glsl";
22
+ export { WebGLBackend } from "./webgl2/WebGLBackend";
23
+ // WebGPU-specific postprocess utilities
24
+ export {
25
+ type PostProcess,
26
+ PostProcessDefaults,
27
+ } from "./webgpu/postprocess/mod";
28
+ // Concrete backend implementations
29
+ export { WebGPUBackend } from "./webgpu/WebGPUBackend";
@@ -0,0 +1,256 @@
1
+ import type { Color } from "../../coreTypes/Color";
2
+ import type { EngineUniform } from "../../coreTypes/EngineUniform";
3
+ import type { Size } from "../../coreTypes/Size";
4
+ import type { Limits, LimitsOptions } from "../../limits";
5
+ import { DEFAULT_LIMITS } from "../../limits";
6
+ import type { CpuTextureAtlas } from "../../textures/types";
7
+ import { assert } from "../../utils/assert";
8
+ import type { IBackendShader, QuadShaderCreationOpts } from "../IBackendShader";
9
+ import type { IRenderBackend } from "../IRenderBackend";
10
+ import type {
11
+ ITextureAtlas,
12
+ TextureAtlasFormat,
13
+ TextureAtlasOptions,
14
+ } from "../ITextureAtlas";
15
+ import { WebGLQuadShader } from "./WebGLQuadShader";
16
+
17
+ export type WebGLBackendOptions = {
18
+ limits?: LimitsOptions;
19
+ format?: "rgba8unorm" | "rg8unorm";
20
+ };
21
+
22
+ /**
23
+ * WebGL 2 implementation of the render backend.
24
+ */
25
+ export class WebGLBackend implements IRenderBackend {
26
+ readonly type = "webgl2" as const;
27
+ readonly limits: Limits;
28
+ readonly atlasSize: Size;
29
+ readonly defaultAtlasId = "default";
30
+
31
+ #atlases = new Map<string, ITextureAtlas>();
32
+ #gl: WebGL2RenderingContext;
33
+ #canvas: HTMLCanvasElement;
34
+
35
+ private constructor(
36
+ gl: WebGL2RenderingContext,
37
+ canvas: HTMLCanvasElement,
38
+ limits: Limits,
39
+ ) {
40
+ this.#gl = gl;
41
+ this.#canvas = canvas;
42
+ this.limits = limits;
43
+ this.atlasSize = {
44
+ width: limits.textureSize,
45
+ height: limits.textureSize,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Create a WebGL 2 backend attached to a canvas.
51
+ */
52
+ static async create(
53
+ canvas: HTMLCanvasElement,
54
+ options: WebGLBackendOptions = {},
55
+ ): Promise<WebGLBackend> {
56
+ const gl = canvas.getContext("webgl2", {
57
+ alpha: true,
58
+ antialias: false,
59
+ premultipliedAlpha: true,
60
+ });
61
+
62
+ if (!gl) {
63
+ throw new Error("WebGL 2 not supported");
64
+ }
65
+
66
+ const limits: Limits = {
67
+ ...DEFAULT_LIMITS,
68
+ ...options.limits,
69
+ };
70
+
71
+ const backend = new WebGLBackend(gl, canvas, limits);
72
+
73
+ // Create the default texture atlas
74
+ backend.createTextureAtlas("default", {
75
+ format: options.format ?? "rgba8unorm",
76
+ layers: limits.textureArrayLayers,
77
+ size: limits.textureSize,
78
+ });
79
+
80
+ return backend;
81
+ }
82
+
83
+ startFrame(clearColor: Color, loadOp: "clear" | "load"): void {
84
+ const gl = this.#gl;
85
+
86
+ // Set viewport to canvas size
87
+ gl.viewport(0, 0, this.#canvas.width, this.#canvas.height);
88
+
89
+ if (loadOp === "clear") {
90
+ gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
91
+ gl.clear(gl.COLOR_BUFFER_BIT);
92
+ }
93
+
94
+ // Enable blending
95
+ gl.enable(gl.BLEND);
96
+ gl.blendFuncSeparate(
97
+ gl.SRC_ALPHA,
98
+ gl.ONE_MINUS_SRC_ALPHA,
99
+ gl.ONE,
100
+ gl.ONE_MINUS_SRC_ALPHA,
101
+ );
102
+ }
103
+
104
+ endFrame(): void {
105
+ const gl = this.#gl;
106
+ gl.flush();
107
+ }
108
+
109
+ updateEngineUniform(_uniform: EngineUniform): void {
110
+ // Uniforms are updated per-shader in WebGL, not at the backend level
111
+ }
112
+
113
+ async uploadAtlas(
114
+ atlas: CpuTextureAtlas,
115
+ layerIndex: number,
116
+ atlasId?: string,
117
+ ): Promise<void> {
118
+ const gl = this.#gl;
119
+ const targetAtlas = this.getTextureAtlas(atlasId ?? "default");
120
+ assert(targetAtlas, `Atlas "${atlasId ?? "default"}" not found`);
121
+ const texture = targetAtlas.handle as WebGLTexture;
122
+
123
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
124
+
125
+ if (atlas.rg8Bytes) {
126
+ // Upload raw bytes for RG8 format
127
+ gl.texSubImage3D(
128
+ gl.TEXTURE_2D_ARRAY,
129
+ 0, // mip level
130
+ 0,
131
+ 0,
132
+ layerIndex, // x, y, z offset
133
+ targetAtlas.size,
134
+ targetAtlas.size,
135
+ 1, // width, height, depth
136
+ gl.RG,
137
+ gl.UNSIGNED_BYTE,
138
+ atlas.rg8Bytes,
139
+ );
140
+ } else {
141
+ // Upload ImageBitmap for RGBA format
142
+ gl.texSubImage3D(
143
+ gl.TEXTURE_2D_ARRAY,
144
+ 0,
145
+ 0,
146
+ 0,
147
+ layerIndex,
148
+ atlas.texture.width,
149
+ atlas.texture.height,
150
+ 1,
151
+ gl.RGBA,
152
+ gl.UNSIGNED_BYTE,
153
+ atlas.texture,
154
+ );
155
+ }
156
+
157
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
158
+ }
159
+
160
+ createQuadShader(opts: QuadShaderCreationOpts): IBackendShader {
161
+ return new WebGLQuadShader(
162
+ opts.label,
163
+ this,
164
+ opts.instanceCount,
165
+ opts.userCode,
166
+ opts.atlasId,
167
+ );
168
+ }
169
+
170
+ createTextureAtlas(id: string, options?: TextureAtlasOptions): ITextureAtlas {
171
+ if (this.#atlases.has(id)) {
172
+ throw new Error(`Atlas "${id}" already exists`);
173
+ }
174
+
175
+ const gl = this.#gl;
176
+ const format: TextureAtlasFormat = options?.format ?? "rgba8unorm";
177
+ const layers = options?.layers ?? this.limits.textureArrayLayers;
178
+ const size = options?.size ?? this.limits.textureSize;
179
+
180
+ const texture = gl.createTexture();
181
+ assert(texture, "Failed to create WebGL texture");
182
+
183
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
184
+
185
+ // Configure texture parameters
186
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
187
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
188
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
189
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
190
+
191
+ // Allocate storage for texture array
192
+ const internalFormat = format === "rg8unorm" ? gl.RG8 : gl.RGBA8;
193
+
194
+ gl.texStorage3D(
195
+ gl.TEXTURE_2D_ARRAY,
196
+ 1, // mip levels
197
+ internalFormat,
198
+ size,
199
+ size,
200
+ layers,
201
+ );
202
+
203
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
204
+
205
+ const atlas: ITextureAtlas = { id, format, layers, size, handle: texture };
206
+ this.#atlases.set(id, atlas);
207
+ return atlas;
208
+ }
209
+
210
+ getTextureAtlas(id: string): ITextureAtlas | null {
211
+ return this.#atlases.get(id) ?? null;
212
+ }
213
+
214
+ destroyTextureAtlas(id: string): void {
215
+ const atlas = this.#atlases.get(id);
216
+ if (atlas) {
217
+ this.#gl.deleteTexture(atlas.handle as WebGLTexture);
218
+ this.#atlases.delete(id);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Get the default texture atlas handle.
224
+ * @deprecated Use getTextureAtlas("default").handle instead
225
+ */
226
+ get textureArrayHandle(): WebGLTexture {
227
+ return this.getTextureAtlas("default")!.handle as WebGLTexture;
228
+ }
229
+
230
+ resize(_width: number, _height: number): void {
231
+ // Canvas resize is handled by the application
232
+ }
233
+
234
+ destroy(): void {
235
+ const gl = this.#gl;
236
+ // Destroy all atlases
237
+ for (const atlas of this.#atlases.values()) {
238
+ gl.deleteTexture(atlas.handle as WebGLTexture);
239
+ }
240
+ this.#atlases.clear();
241
+ }
242
+
243
+ /**
244
+ * Get the WebGL 2 rendering context.
245
+ */
246
+ get gl(): WebGL2RenderingContext {
247
+ return this.#gl;
248
+ }
249
+
250
+ /**
251
+ * Get the presentation format (of the default atlas).
252
+ */
253
+ get presentationFormat(): TextureAtlasFormat {
254
+ return this.getTextureAtlas("default")?.format ?? "rgba8unorm";
255
+ }
256
+ }