@quake2ts/engine 0.0.837 → 0.0.838

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.
@@ -9614,6 +9614,2111 @@ var createRenderer = (gl) => {
9614
9614
  }
9615
9615
  };
9616
9616
  };
9617
+
9618
+ // src/render/webgpu/context.ts
9619
+ async function createWebGPUContext(canvas, options) {
9620
+ if (!navigator.gpu) {
9621
+ throw new Error("WebGPU is not supported in this environment");
9622
+ }
9623
+ const adapter = await navigator.gpu.requestAdapter({
9624
+ powerPreference: options?.powerPreference || "high-performance"
9625
+ });
9626
+ if (!adapter) {
9627
+ throw new Error("No appropriate GPUAdapter found");
9628
+ }
9629
+ if (options?.requiredFeatures) {
9630
+ for (const feature of options.requiredFeatures) {
9631
+ if (!adapter.features.has(feature)) {
9632
+ throw new Error(`Required feature not available: ${feature}`);
9633
+ }
9634
+ }
9635
+ }
9636
+ const deviceDescriptor = {
9637
+ requiredFeatures: options?.requiredFeatures,
9638
+ requiredLimits: options?.requiredLimits
9639
+ };
9640
+ const device = await adapter.requestDevice(deviceDescriptor);
9641
+ let context;
9642
+ let format = "rgba8unorm";
9643
+ const depthFormat = "depth24plus";
9644
+ let isHeadless = true;
9645
+ let width = options?.width || 800;
9646
+ let height = options?.height || 600;
9647
+ if (canvas) {
9648
+ context = canvas.getContext("webgpu");
9649
+ if (!context) {
9650
+ throw new Error("Failed to get WebGPU context from canvas");
9651
+ }
9652
+ isHeadless = false;
9653
+ format = navigator.gpu.getPreferredCanvasFormat();
9654
+ width = canvas.width;
9655
+ height = canvas.height;
9656
+ context.configure({
9657
+ device,
9658
+ format,
9659
+ alphaMode: "opaque"
9660
+ // Standard for game rendering
9661
+ });
9662
+ }
9663
+ const features = /* @__PURE__ */ new Set();
9664
+ for (const feature of adapter.features) {
9665
+ features.add(feature);
9666
+ }
9667
+ return {
9668
+ adapter,
9669
+ device,
9670
+ context,
9671
+ format,
9672
+ depthFormat,
9673
+ features,
9674
+ limits: device.limits,
9675
+ isHeadless,
9676
+ width,
9677
+ height
9678
+ };
9679
+ }
9680
+ function queryCapabilities(state) {
9681
+ const { features, limits } = state;
9682
+ return {
9683
+ hasTimestampQuery: features.has("timestamp-query"),
9684
+ hasDepthClipControl: features.has("depth-clip-control"),
9685
+ hasTextureCompressionBC: features.has("texture-compression-bc"),
9686
+ hasTextureCompressionETC2: features.has("texture-compression-etc2"),
9687
+ hasTextureCompressionASTC: features.has("texture-compression-astc"),
9688
+ maxTextureDimension2D: limits.maxTextureDimension2D,
9689
+ maxBindGroups: limits.maxBindGroups,
9690
+ maxUniformBufferBindingSize: limits.maxUniformBufferBindingSize,
9691
+ maxStorageBufferBindingSize: limits.maxStorageBufferBindingSize
9692
+ };
9693
+ }
9694
+ function evaluateLightStyle2(pattern, time) {
9695
+ if (!pattern) return 1;
9696
+ const frame = Math.floor(time * 10) % pattern.length;
9697
+ const charCode = pattern.charCodeAt(frame);
9698
+ return (charCode - 97) / 12;
9699
+ }
9700
+ function sortVisibleFacesFrontToBack2(faces) {
9701
+ return [...faces].sort((a, b) => b.sortKey - a.sortKey);
9702
+ }
9703
+ function sortVisibleFacesBackToFront2(faces) {
9704
+ return [...faces].sort((a, b) => a.sortKey - b.sortKey);
9705
+ }
9706
+ var FrameRenderer = class {
9707
+ constructor(context, pipelines) {
9708
+ this.context = context;
9709
+ this.pipelines = pipelines;
9710
+ this.depthTexture = null;
9711
+ this.copyTexture = null;
9712
+ // Separate texture for headless output if no context exists
9713
+ this.headlessTarget = null;
9714
+ this.lastWidth = 0;
9715
+ this.lastHeight = 0;
9716
+ this.lastFrameTime = 0;
9717
+ // Current frame context (available during frame rendering)
9718
+ this.currentFrameContext = null;
9719
+ }
9720
+ ensureDepthTexture(width, height) {
9721
+ if (this.depthTexture && this.lastWidth === width && this.lastHeight === height) {
9722
+ return this.depthTexture.createView();
9723
+ }
9724
+ if (this.depthTexture) {
9725
+ this.depthTexture.destroy();
9726
+ }
9727
+ this.depthTexture = this.context.device.createTexture({
9728
+ size: [width, height],
9729
+ format: "depth24plus",
9730
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
9731
+ label: "depth-buffer"
9732
+ });
9733
+ if (this.copyTexture) {
9734
+ this.copyTexture.destroy();
9735
+ this.copyTexture = null;
9736
+ }
9737
+ this.lastWidth = width;
9738
+ this.lastHeight = height;
9739
+ return this.depthTexture.createView();
9740
+ }
9741
+ ensureCopyTexture(width, height) {
9742
+ if (this.copyTexture && this.lastWidth === width && this.lastHeight === height) {
9743
+ return this.copyTexture;
9744
+ }
9745
+ if (this.copyTexture) {
9746
+ this.copyTexture.destroy();
9747
+ }
9748
+ this.copyTexture = this.context.device.createTexture({
9749
+ size: [width, height],
9750
+ format: this.context.format,
9751
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
9752
+ label: "frame-copy-texture"
9753
+ });
9754
+ return this.copyTexture;
9755
+ }
9756
+ ensureHeadlessTarget(width, height) {
9757
+ if (this.headlessTarget && this.headlessTarget.width === width && this.headlessTarget.height === height) {
9758
+ return this.headlessTarget.createView();
9759
+ }
9760
+ if (this.headlessTarget) {
9761
+ this.headlessTarget.destroy();
9762
+ }
9763
+ this.headlessTarget = this.context.device.createTexture({
9764
+ size: [width, height],
9765
+ format: this.context.format,
9766
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING,
9767
+ label: "headless-render-target"
9768
+ });
9769
+ return this.headlessTarget.createView();
9770
+ }
9771
+ beginFrame() {
9772
+ const { device, context, width, height } = this.context;
9773
+ const commandEncoder = device.createCommandEncoder({ label: "frame-command-encoder" });
9774
+ let renderTarget;
9775
+ if (context) {
9776
+ renderTarget = context.getCurrentTexture().createView();
9777
+ } else {
9778
+ renderTarget = this.ensureHeadlessTarget(width, height);
9779
+ }
9780
+ const depthTexture = this.ensureDepthTexture(width, height);
9781
+ return {
9782
+ device,
9783
+ commandEncoder,
9784
+ renderTarget,
9785
+ depthTexture,
9786
+ width,
9787
+ height
9788
+ };
9789
+ }
9790
+ /**
9791
+ * Begin 2D rendering pass. Called by WebGPURenderer.begin2D()
9792
+ */
9793
+ begin2DPass() {
9794
+ if (!this.currentFrameContext) {
9795
+ throw new Error("begin2DPass called outside of renderFrame");
9796
+ }
9797
+ const { commandEncoder, renderTarget, width, height } = this.currentFrameContext;
9798
+ this.pipelines.sprite.setProjection(width, height);
9799
+ this.pipelines.sprite.begin(commandEncoder, renderTarget);
9800
+ }
9801
+ /**
9802
+ * End 2D rendering pass. Called by WebGPURenderer.end2D()
9803
+ */
9804
+ end2DPass() {
9805
+ this.pipelines.sprite.end();
9806
+ }
9807
+ renderFrame(options) {
9808
+ const now = performance.now();
9809
+ const fps = this.lastFrameTime > 0 ? 1e3 / (now - this.lastFrameTime) : 0;
9810
+ this.lastFrameTime = now;
9811
+ const stats = {
9812
+ batches: 0,
9813
+ facesDrawn: 0,
9814
+ drawCalls: 0,
9815
+ skyDrawn: false,
9816
+ viewModelDrawn: false,
9817
+ fps: Math.round(fps),
9818
+ vertexCount: 0
9819
+ };
9820
+ const frameCtx = this.beginFrame();
9821
+ this.currentFrameContext = frameCtx;
9822
+ const { commandEncoder, renderTarget, depthTexture } = frameCtx;
9823
+ const {
9824
+ clearColor = [0, 0, 0, 1],
9825
+ world,
9826
+ camera,
9827
+ timeSeconds = 0,
9828
+ dlights,
9829
+ renderMode,
9830
+ disableLightmaps,
9831
+ lightmapOnly,
9832
+ brightness,
9833
+ gamma,
9834
+ fullbright,
9835
+ ambient,
9836
+ lightStyleOverrides,
9837
+ portalState
9838
+ } = options;
9839
+ const viewProjection = new Float32Array(options.camera.viewProjectionMatrix);
9840
+ const opaquePassDescriptor = {
9841
+ colorAttachments: [{
9842
+ view: renderTarget,
9843
+ clearValue: clearColor,
9844
+ loadOp: "clear",
9845
+ storeOp: "store"
9846
+ }],
9847
+ depthStencilAttachment: {
9848
+ view: depthTexture,
9849
+ depthClearValue: 1,
9850
+ depthLoadOp: "clear",
9851
+ depthStoreOp: "store"
9852
+ },
9853
+ label: "opaque-render-pass"
9854
+ };
9855
+ const opaquePass = commandEncoder.beginRenderPass(opaquePassDescriptor);
9856
+ if (options.sky && options.sky.cubemap) {
9857
+ {
9858
+ const cameraState = options.cameraState ?? options.camera.toState();
9859
+ const scroll = computeSkyScroll(options.timeSeconds ?? 0, options.sky.scrollSpeeds ?? [0.01, 0.02]);
9860
+ this.pipelines.skybox.draw(opaquePass, {
9861
+ cameraState,
9862
+ // NEW: let pipeline build matrices
9863
+ scroll,
9864
+ cubemap: options.sky.cubemap
9865
+ });
9866
+ }
9867
+ stats.skyDrawn = true;
9868
+ }
9869
+ const opaqueFaces = [];
9870
+ const transparentFaces = [];
9871
+ if (world) {
9872
+ world.materials?.update(timeSeconds);
9873
+ const frustum = extractFrustumPlanes(Array.from(viewProjection));
9874
+ const cameraPosition = {
9875
+ x: camera.position[0] ?? 0,
9876
+ y: camera.position[1] ?? 0,
9877
+ z: camera.position[2] ?? 0
9878
+ };
9879
+ const visibleFaces = gatherVisibleFaces(world.map, cameraPosition, frustum, portalState);
9880
+ for (const face of visibleFaces) {
9881
+ const geometry = world.surfaces[face.faceIndex];
9882
+ if (!geometry) continue;
9883
+ const isTransparent = (geometry.surfaceFlags & (shared.SURF_TRANS33 | shared.SURF_TRANS66 | shared.SURF_WARP)) !== 0;
9884
+ if (isTransparent) {
9885
+ transparentFaces.push(face);
9886
+ } else {
9887
+ opaqueFaces.push(face);
9888
+ }
9889
+ }
9890
+ const sortedOpaque = sortVisibleFacesFrontToBack2(opaqueFaces);
9891
+ let effectiveLightStyles = world.lightStyles || [];
9892
+ if (lightStyleOverrides && lightStyleOverrides.size > 0) {
9893
+ const styles = [...world.lightStyles || []];
9894
+ for (const [index, pattern] of lightStyleOverrides) {
9895
+ while (styles.length <= index) styles.push(1);
9896
+ styles[index] = evaluateLightStyle2(pattern, timeSeconds);
9897
+ }
9898
+ effectiveLightStyles = styles;
9899
+ }
9900
+ const drawSurfaceBatch = (faces, pass, lightStyles) => {
9901
+ for (const { faceIndex } of faces) {
9902
+ const geometry = world.surfaces[faceIndex];
9903
+ if (!geometry) continue;
9904
+ if ((geometry.surfaceFlags & shared.SURF_SKY) !== 0) continue;
9905
+ const faceStyles = world.map.faces[faceIndex]?.styles;
9906
+ this.pipelines.bsp.bind(pass, {
9907
+ modelViewProjection: viewProjection,
9908
+ styleIndices: faceStyles,
9909
+ styleValues: lightStyles,
9910
+ surfaceFlags: geometry.surfaceFlags,
9911
+ timeSeconds,
9912
+ // diffuseTexture: diffuse?.gpuTexture.createView(), // Need view
9913
+ // diffuseSampler: ...
9914
+ // lightmapTexture: ...
9915
+ // lightmapSampler: ...
9916
+ dlights,
9917
+ renderMode,
9918
+ lightmapOnly,
9919
+ brightness,
9920
+ gamma,
9921
+ fullbright,
9922
+ ambient,
9923
+ cameraPosition: options.camera.position
9924
+ });
9925
+ this.pipelines.bsp.draw(pass, geometry, renderMode);
9926
+ stats.facesDrawn++;
9927
+ stats.drawCalls++;
9928
+ stats.vertexCount += geometry.vertexCount;
9929
+ }
9930
+ };
9931
+ drawSurfaceBatch(sortedOpaque, opaquePass, effectiveLightStyles);
9932
+ opaquePass.end();
9933
+ const transparentPassDescriptor = {
9934
+ colorAttachments: [{
9935
+ view: renderTarget,
9936
+ loadOp: "load",
9937
+ storeOp: "store"
9938
+ }],
9939
+ depthStencilAttachment: {
9940
+ view: depthTexture,
9941
+ depthLoadOp: "load",
9942
+ depthStoreOp: "store"
9943
+ },
9944
+ label: "transparent-render-pass"
9945
+ };
9946
+ const transparentPass = commandEncoder.beginRenderPass(transparentPassDescriptor);
9947
+ if (transparentFaces.length > 0) {
9948
+ const sortedTransparent = sortVisibleFacesBackToFront2(transparentFaces);
9949
+ drawSurfaceBatch(sortedTransparent, transparentPass, effectiveLightStyles);
9950
+ }
9951
+ transparentPass.end();
9952
+ } else {
9953
+ opaquePass.end();
9954
+ }
9955
+ if (options.underwaterWarp || options.bloom) ;
9956
+ if (options.onDraw2D) {
9957
+ options.onDraw2D();
9958
+ }
9959
+ if (this.pipelines.sprite.isActive) {
9960
+ console.warn("2D render pass was not properly closed - auto-closing to prevent resource leak");
9961
+ this.end2DPass();
9962
+ }
9963
+ this.context.device.queue.submit([commandEncoder.finish()]);
9964
+ this.currentFrameContext = null;
9965
+ return stats;
9966
+ }
9967
+ };
9968
+
9969
+ // src/render/webgpu/shaders/mipmapShader.ts
9970
+ var MIPMAP_SHADER = `
9971
+ struct VSOutput {
9972
+ @builtin(position) position: vec4f,
9973
+ @location(0) texcoord: vec2f,
9974
+ };
9975
+
9976
+ @vertex
9977
+ fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> VSOutput {
9978
+ var pos = array<vec2f, 6>(
9979
+ vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(-1.0, 1.0),
9980
+ vec2f(-1.0, 1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0)
9981
+ );
9982
+
9983
+ var output: VSOutput;
9984
+ output.position = vec4f(pos[vertexIndex], 0.0, 1.0);
9985
+ // Remap from [-1, 1] to [0, 1] for texcoord, Y is flipped in WebGPU clip space vs texture UV
9986
+ // Clip space: (-1,-1) is bottom-left. UV: (0,0) is top-left usually?
9987
+ // Wait, WebGPU clip space Y is up. UV (0,0) is top-left.
9988
+ // Quad vertices are full screen.
9989
+ output.texcoord = pos[vertexIndex] * vec2f(0.5, -0.5) + vec2f(0.5, 0.5);
9990
+ return output;
9991
+ }
9992
+
9993
+ @group(0) @binding(0) var imgSampler: sampler;
9994
+ @group(0) @binding(1) var img: texture_2d<f32>;
9995
+
9996
+ @fragment
9997
+ fn fs_main(in: VSOutput) -> @location(0) vec4f {
9998
+ return textureSample(img, imgSampler, in.texcoord);
9999
+ }
10000
+ `;
10001
+ var GPUBufferResource = class {
10002
+ constructor(device, descriptor) {
10003
+ this.device = device;
10004
+ this.size = Math.ceil(descriptor.size / 4) * 4;
10005
+ this.usage = descriptor.usage;
10006
+ this.buffer = device.createBuffer({
10007
+ size: this.size,
10008
+ usage: this.usage,
10009
+ label: descriptor.label,
10010
+ mappedAtCreation: descriptor.mappedAtCreation
10011
+ });
10012
+ }
10013
+ write(data, offset = 0) {
10014
+ const dataSize = data.byteLength;
10015
+ if (offset + dataSize > this.size) {
10016
+ throw new Error(`Buffer write out of bounds: offset ${offset} + data ${dataSize} > buffer ${this.size}`);
10017
+ }
10018
+ this.device.queue.writeBuffer(
10019
+ this.buffer,
10020
+ offset,
10021
+ data,
10022
+ 0
10023
+ // dataOffset
10024
+ );
10025
+ }
10026
+ async mapAsync(mode, offset = 0, size) {
10027
+ await this.buffer.mapAsync(mode, offset, size);
10028
+ }
10029
+ getMappedRange(offset = 0, size) {
10030
+ return this.buffer.getMappedRange(offset, size);
10031
+ }
10032
+ unmap() {
10033
+ this.buffer.unmap();
10034
+ }
10035
+ destroy() {
10036
+ this.buffer.destroy();
10037
+ }
10038
+ };
10039
+ var VertexBuffer2 = class extends GPUBufferResource {
10040
+ constructor(device, descriptor) {
10041
+ super(device, {
10042
+ size: descriptor.size,
10043
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST | (descriptor.usage || 0),
10044
+ label: descriptor.label
10045
+ });
10046
+ }
10047
+ };
10048
+ var IndexBuffer2 = class extends GPUBufferResource {
10049
+ constructor(device, descriptor) {
10050
+ super(device, {
10051
+ size: descriptor.size,
10052
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST | (descriptor.usage || 0),
10053
+ label: descriptor.label
10054
+ });
10055
+ }
10056
+ };
10057
+ var UniformBuffer = class extends GPUBufferResource {
10058
+ constructor(device, descriptor) {
10059
+ super(device, {
10060
+ size: descriptor.size,
10061
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | (descriptor.usage || 0),
10062
+ label: descriptor.label
10063
+ });
10064
+ }
10065
+ };
10066
+ function getBlockSize(format) {
10067
+ switch (format) {
10068
+ case "rgba8unorm":
10069
+ case "rgba8unorm-srgb":
10070
+ case "bgra8unorm":
10071
+ case "bgra8unorm-srgb":
10072
+ case "rgba8sint":
10073
+ case "rgba8uint":
10074
+ return 4;
10075
+ case "rg8unorm":
10076
+ case "rg8sint":
10077
+ case "rg8uint":
10078
+ case "r16float":
10079
+ case "r16sint":
10080
+ case "r16uint":
10081
+ return 2;
10082
+ case "r8unorm":
10083
+ case "r8sint":
10084
+ case "r8uint":
10085
+ return 1;
10086
+ case "rgba16float":
10087
+ case "rgba16sint":
10088
+ case "rgba16uint":
10089
+ case "rgba32float":
10090
+ return 8;
10091
+ // wait, rgba16 is 8 bytes. rgba32 is 16 bytes.
10092
+ case "rgba32float":
10093
+ case "rgba32sint":
10094
+ case "rgba32uint":
10095
+ return 16;
10096
+ case "depth24plus":
10097
+ case "depth24plus-stencil8":
10098
+ // approximate
10099
+ case "depth32float":
10100
+ return 4;
10101
+ default:
10102
+ return 4;
10103
+ }
10104
+ }
10105
+ var _Texture2D = class _Texture2D {
10106
+ constructor(device, descriptor) {
10107
+ this.device = device;
10108
+ this.width = descriptor.width;
10109
+ this.height = descriptor.height;
10110
+ this.format = descriptor.format;
10111
+ const usage = descriptor.usage ?? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
10112
+ this.texture = device.createTexture({
10113
+ size: [this.width, this.height, 1],
10114
+ format: this.format,
10115
+ usage,
10116
+ mipLevelCount: descriptor.mipLevelCount ?? 1,
10117
+ label: descriptor.label
10118
+ });
10119
+ }
10120
+ upload(data, options = {}) {
10121
+ const width = options.width ?? this.width;
10122
+ const height = options.height ?? this.height;
10123
+ const depthOrArrayLayers = options.depthOrArrayLayers ?? 1;
10124
+ const mipLevel = options.mipLevel ?? 0;
10125
+ const blockSize = getBlockSize(this.format);
10126
+ let bytesPerRow = options.bytesPerRow;
10127
+ if (!bytesPerRow) {
10128
+ bytesPerRow = width * blockSize;
10129
+ }
10130
+ this.device.queue.writeTexture(
10131
+ { texture: this.texture, mipLevel, origin: { x: 0, y: 0, z: 0 } },
10132
+ data,
10133
+ {
10134
+ offset: 0,
10135
+ bytesPerRow,
10136
+ rowsPerImage: options.rowsPerImage ?? height
10137
+ },
10138
+ { width, height, depthOrArrayLayers }
10139
+ );
10140
+ }
10141
+ getMipmapPipeline(format) {
10142
+ let devicePipelines = _Texture2D.mipmapPipelines.get(this.device);
10143
+ if (!devicePipelines) {
10144
+ devicePipelines = /* @__PURE__ */ new Map();
10145
+ _Texture2D.mipmapPipelines.set(this.device, devicePipelines);
10146
+ }
10147
+ let pipeline = devicePipelines.get(format);
10148
+ if (!pipeline) {
10149
+ const module = this.device.createShaderModule({
10150
+ code: MIPMAP_SHADER,
10151
+ label: "mipmap-shader"
10152
+ });
10153
+ pipeline = this.device.createRenderPipeline({
10154
+ layout: "auto",
10155
+ vertex: {
10156
+ module,
10157
+ entryPoint: "vs_main"
10158
+ },
10159
+ fragment: {
10160
+ module,
10161
+ entryPoint: "fs_main",
10162
+ targets: [{ format }]
10163
+ },
10164
+ primitive: {
10165
+ topology: "triangle-list"
10166
+ },
10167
+ label: `mipmap-pipeline-${format}`
10168
+ });
10169
+ devicePipelines.set(format, pipeline);
10170
+ }
10171
+ return pipeline;
10172
+ }
10173
+ getMipmapSampler() {
10174
+ let sampler = _Texture2D.mipmapSamplers.get(this.device);
10175
+ if (!sampler) {
10176
+ sampler = this.device.createSampler({
10177
+ minFilter: "linear",
10178
+ magFilter: "linear",
10179
+ label: "mipmap-sampler"
10180
+ });
10181
+ _Texture2D.mipmapSamplers.set(this.device, sampler);
10182
+ }
10183
+ return sampler;
10184
+ }
10185
+ generateMipmaps(commandEncoder) {
10186
+ const mipCount = this.texture.mipLevelCount;
10187
+ if (mipCount <= 1) return;
10188
+ const pipeline = this.getMipmapPipeline(this.format);
10189
+ const sampler = this.getMipmapSampler();
10190
+ for (let i = 1; i < mipCount; i++) {
10191
+ const srcView = this.texture.createView({
10192
+ baseMipLevel: i - 1,
10193
+ mipLevelCount: 1,
10194
+ label: `mipmap-src-${i - 1}`
10195
+ });
10196
+ const dstView = this.texture.createView({
10197
+ baseMipLevel: i,
10198
+ mipLevelCount: 1,
10199
+ label: `mipmap-dst-${i}`
10200
+ });
10201
+ const passEncoder = commandEncoder.beginRenderPass({
10202
+ colorAttachments: [{
10203
+ view: dstView,
10204
+ loadOp: "clear",
10205
+ storeOp: "store"
10206
+ }],
10207
+ label: `mipmap-pass-${i}`
10208
+ });
10209
+ const bindGroup = this.device.createBindGroup({
10210
+ layout: pipeline.getBindGroupLayout(0),
10211
+ entries: [
10212
+ { binding: 0, resource: sampler },
10213
+ { binding: 1, resource: srcView }
10214
+ ],
10215
+ label: `mipmap-bindgroup-${i}`
10216
+ });
10217
+ passEncoder.setPipeline(pipeline);
10218
+ passEncoder.setBindGroup(0, bindGroup);
10219
+ passEncoder.draw(6);
10220
+ passEncoder.end();
10221
+ }
10222
+ }
10223
+ createView(descriptor) {
10224
+ return this.texture.createView(descriptor);
10225
+ }
10226
+ destroy() {
10227
+ this.texture.destroy();
10228
+ }
10229
+ get memorySize() {
10230
+ const blockSize = getBlockSize(this.format);
10231
+ let size = 0;
10232
+ let w = this.width;
10233
+ let h = this.height;
10234
+ const mipCount = this.texture.mipLevelCount;
10235
+ for (let i = 0; i < mipCount; i++) {
10236
+ size += w * h * blockSize;
10237
+ w = Math.max(1, Math.floor(w / 2));
10238
+ h = Math.max(1, Math.floor(h / 2));
10239
+ }
10240
+ return size;
10241
+ }
10242
+ };
10243
+ // Cache pipelines per device and per format
10244
+ _Texture2D.mipmapPipelines = /* @__PURE__ */ new WeakMap();
10245
+ _Texture2D.mipmapSamplers = /* @__PURE__ */ new WeakMap();
10246
+ var Texture2D2 = _Texture2D;
10247
+ var Sampler = class {
10248
+ constructor(device, descriptor) {
10249
+ this.sampler = device.createSampler({
10250
+ ...descriptor,
10251
+ label: descriptor.label
10252
+ });
10253
+ }
10254
+ destroy() {
10255
+ }
10256
+ };
10257
+ function createLinearSampler(device) {
10258
+ return new Sampler(device, {
10259
+ minFilter: "linear",
10260
+ magFilter: "linear",
10261
+ mipmapFilter: "linear",
10262
+ label: "linear-sampler"
10263
+ });
10264
+ }
10265
+ var ShaderModule = class {
10266
+ constructor(device, descriptor) {
10267
+ this.device = device;
10268
+ this.module = device.createShaderModule({
10269
+ code: descriptor.code,
10270
+ label: descriptor.label
10271
+ });
10272
+ }
10273
+ get compilationInfo() {
10274
+ return this.module.getCompilationInfo();
10275
+ }
10276
+ };
10277
+ var RenderPipeline = class {
10278
+ constructor(device, descriptor) {
10279
+ this.device = device;
10280
+ const layout = descriptor.layout;
10281
+ this.pipeline = device.createRenderPipeline({
10282
+ layout,
10283
+ vertex: {
10284
+ module: descriptor.vertex.module.module,
10285
+ entryPoint: descriptor.vertex.entryPoint,
10286
+ buffers: descriptor.vertex.buffers
10287
+ },
10288
+ fragment: descriptor.fragment ? {
10289
+ module: descriptor.fragment.module.module,
10290
+ entryPoint: descriptor.fragment.entryPoint,
10291
+ targets: descriptor.fragment.targets
10292
+ } : void 0,
10293
+ primitive: descriptor.primitive,
10294
+ depthStencil: descriptor.depthStencil,
10295
+ multisample: descriptor.multisample,
10296
+ label: descriptor.label
10297
+ });
10298
+ }
10299
+ get layout() {
10300
+ throw new Error("Cannot retrieve layout from pipeline created with 'auto' layout");
10301
+ }
10302
+ destroy() {
10303
+ }
10304
+ };
10305
+ var BindGroupLayout = class {
10306
+ constructor(device, descriptor) {
10307
+ this.device = device;
10308
+ this.layout = device.createBindGroupLayout({
10309
+ entries: descriptor.entries,
10310
+ label: descriptor.label
10311
+ });
10312
+ }
10313
+ };
10314
+ var BindGroup = class {
10315
+ constructor(device, layout, entries, label) {
10316
+ this.device = device;
10317
+ const gpuEntries = entries.map((entry) => {
10318
+ let resource;
10319
+ if (entry.resource instanceof GPUBufferResource) {
10320
+ resource = { buffer: entry.resource.buffer };
10321
+ } else if (entry.resource instanceof Sampler) {
10322
+ resource = entry.resource.sampler;
10323
+ } else {
10324
+ resource = entry.resource;
10325
+ }
10326
+ return {
10327
+ binding: entry.binding,
10328
+ resource
10329
+ };
10330
+ });
10331
+ this.bindGroup = device.createBindGroup({
10332
+ layout: layout.layout,
10333
+ entries: gpuEntries,
10334
+ label
10335
+ });
10336
+ }
10337
+ destroy() {
10338
+ }
10339
+ };
10340
+ var BindGroupBuilder = class {
10341
+ constructor(label) {
10342
+ this.label = label;
10343
+ this.entries = [];
10344
+ }
10345
+ addUniformBuffer(binding, visibility) {
10346
+ this.entries.push({
10347
+ binding,
10348
+ visibility,
10349
+ buffer: { type: "uniform" }
10350
+ });
10351
+ return this;
10352
+ }
10353
+ addStorageBuffer(binding, visibility, type = "read-only-storage") {
10354
+ this.entries.push({
10355
+ binding,
10356
+ visibility,
10357
+ buffer: { type }
10358
+ });
10359
+ return this;
10360
+ }
10361
+ addTexture(binding, visibility, sampleType = "float", viewDimension = "2d") {
10362
+ this.entries.push({
10363
+ binding,
10364
+ visibility,
10365
+ texture: { sampleType, viewDimension }
10366
+ });
10367
+ return this;
10368
+ }
10369
+ addSampler(binding, visibility, type = "filtering") {
10370
+ this.entries.push({
10371
+ binding,
10372
+ visibility,
10373
+ sampler: { type }
10374
+ });
10375
+ return this;
10376
+ }
10377
+ build(device) {
10378
+ return new BindGroupLayout(device, {
10379
+ entries: this.entries,
10380
+ label: this.label
10381
+ });
10382
+ }
10383
+ };
10384
+
10385
+ // src/render/webgpu/shaders/spriteShader.ts
10386
+ var SPRITE_SHADER = `
10387
+ // Sprite Rendering Shader (WGSL)
10388
+
10389
+ struct VertexInput {
10390
+ @location(0) position: vec2f,
10391
+ @location(1) texcoord: vec2f,
10392
+ @location(2) color: vec4f,
10393
+ }
10394
+
10395
+ struct VertexOutput {
10396
+ @builtin(position) position: vec4f,
10397
+ @location(0) texcoord: vec2f,
10398
+ @location(1) color: vec4f,
10399
+ }
10400
+
10401
+ struct Uniforms {
10402
+ projection: mat4x4f,
10403
+ }
10404
+
10405
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
10406
+
10407
+ @vertex
10408
+ fn vs_main(input: VertexInput) -> VertexOutput {
10409
+ var output: VertexOutput;
10410
+ // Orthographic projection: Input is screen coords (pixels), Z is 0.0, W is 1.0
10411
+ output.position = uniforms.projection * vec4f(input.position, 0.0, 1.0);
10412
+ output.texcoord = input.texcoord;
10413
+ output.color = input.color;
10414
+ return output;
10415
+ }
10416
+
10417
+ @group(1) @binding(0) var texSampler: sampler;
10418
+ @group(1) @binding(1) var tex: texture_2d<f32>;
10419
+
10420
+ @fragment
10421
+ fn fs_main(input: VertexOutput) -> @location(0) vec4f {
10422
+ let texColor = textureSample(tex, texSampler, input.texcoord);
10423
+ return texColor * input.color;
10424
+ }
10425
+
10426
+ @fragment
10427
+ fn fs_solid(input: VertexOutput) -> @location(0) vec4f {
10428
+ return input.color;
10429
+ }
10430
+ `;
10431
+
10432
+ // src/render/webgpu/pipelines/sprite.ts
10433
+ var MAX_SPRITES = 2048;
10434
+ var VERTICES_PER_SPRITE = 4;
10435
+ var INDICES_PER_SPRITE = 6;
10436
+ var FLOATS_PER_VERTEX = 8;
10437
+ var SpriteRenderer2 = class {
10438
+ constructor(device, format) {
10439
+ this.device = device;
10440
+ this.format = format;
10441
+ this.currentVertexCount = 0;
10442
+ this.currentIndexCount = 0;
10443
+ this.currentTexture = null;
10444
+ this.drawCommands = [];
10445
+ this.textureBindGroups = /* @__PURE__ */ new Map();
10446
+ this._activeEncoder = null;
10447
+ this._activeRenderTarget = null;
10448
+ const shaderModule = new ShaderModule(device, {
10449
+ code: SPRITE_SHADER,
10450
+ label: "sprite-shader"
10451
+ });
10452
+ const vertexBufferLayout = {
10453
+ arrayStride: FLOATS_PER_VERTEX * 4,
10454
+ stepMode: "vertex",
10455
+ attributes: [
10456
+ { format: "float32x2", offset: 0, shaderLocation: 0 },
10457
+ // position
10458
+ { format: "float32x2", offset: 8, shaderLocation: 1 },
10459
+ // texcoord
10460
+ { format: "float32x4", offset: 16, shaderLocation: 2 }
10461
+ // color
10462
+ ]
10463
+ };
10464
+ const bindGroupBuilder = new BindGroupBuilder("sprite-uniform-layout");
10465
+ bindGroupBuilder.addUniformBuffer(0, GPUShaderStage.VERTEX);
10466
+ const uniformBindGroupLayout = bindGroupBuilder.build(device);
10467
+ const textureBindGroupBuilder = new BindGroupBuilder("sprite-texture-layout");
10468
+ textureBindGroupBuilder.addSampler(0, GPUShaderStage.FRAGMENT);
10469
+ textureBindGroupBuilder.addTexture(1, GPUShaderStage.FRAGMENT);
10470
+ this.textureBindGroupLayout = textureBindGroupBuilder.build(device);
10471
+ const pipelineLayout = device.createPipelineLayout({
10472
+ bindGroupLayouts: [
10473
+ uniformBindGroupLayout.layout,
10474
+ this.textureBindGroupLayout.layout
10475
+ ],
10476
+ label: "sprite-pipeline-layout"
10477
+ });
10478
+ const pipelineLayoutSolid = device.createPipelineLayout({
10479
+ bindGroupLayouts: [
10480
+ uniformBindGroupLayout.layout
10481
+ ],
10482
+ label: "sprite-pipeline-solid-layout"
10483
+ });
10484
+ const blendState = {
10485
+ color: {
10486
+ srcFactor: "src-alpha",
10487
+ dstFactor: "one-minus-src-alpha",
10488
+ operation: "add"
10489
+ },
10490
+ alpha: {
10491
+ srcFactor: "one",
10492
+ dstFactor: "one-minus-src-alpha",
10493
+ operation: "add"
10494
+ }
10495
+ };
10496
+ this.pipelineTextured = new RenderPipeline(device, {
10497
+ layout: pipelineLayout,
10498
+ vertex: {
10499
+ module: shaderModule,
10500
+ entryPoint: "vs_main",
10501
+ buffers: [vertexBufferLayout]
10502
+ },
10503
+ fragment: {
10504
+ module: shaderModule,
10505
+ entryPoint: "fs_main",
10506
+ targets: [{
10507
+ format: this.format,
10508
+ blend: blendState
10509
+ }]
10510
+ },
10511
+ primitive: {
10512
+ topology: "triangle-list",
10513
+ cullMode: "none"
10514
+ // Sprites are 2D, no culling usually needed
10515
+ },
10516
+ label: "sprite-pipeline-textured"
10517
+ });
10518
+ this.pipelineSolid = new RenderPipeline(device, {
10519
+ layout: pipelineLayoutSolid,
10520
+ vertex: {
10521
+ module: shaderModule,
10522
+ entryPoint: "vs_main",
10523
+ buffers: [vertexBufferLayout]
10524
+ },
10525
+ fragment: {
10526
+ module: shaderModule,
10527
+ entryPoint: "fs_solid",
10528
+ targets: [{
10529
+ format: this.format,
10530
+ blend: blendState
10531
+ }]
10532
+ },
10533
+ primitive: {
10534
+ topology: "triangle-list",
10535
+ cullMode: "none"
10536
+ },
10537
+ label: "sprite-pipeline-solid"
10538
+ });
10539
+ this.vertexBuffer = new VertexBuffer2(device, {
10540
+ size: MAX_SPRITES * VERTICES_PER_SPRITE * FLOATS_PER_VERTEX * 4,
10541
+ label: "sprite-vertex-buffer",
10542
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
10543
+ });
10544
+ this.indexBuffer = new IndexBuffer2(device, {
10545
+ size: MAX_SPRITES * INDICES_PER_SPRITE * 2,
10546
+ // Uint16
10547
+ label: "sprite-index-buffer",
10548
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
10549
+ });
10550
+ this.uniformBuffer = new UniformBuffer(device, {
10551
+ size: 64,
10552
+ // mat4
10553
+ label: "sprite-uniform-buffer",
10554
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
10555
+ });
10556
+ this.uniformBindGroup = new BindGroup(
10557
+ device,
10558
+ uniformBindGroupLayout,
10559
+ [
10560
+ { binding: 0, resource: this.uniformBuffer }
10561
+ ],
10562
+ "sprite-uniform-bind-group"
10563
+ );
10564
+ this.vertexData = new Float32Array(MAX_SPRITES * VERTICES_PER_SPRITE * FLOATS_PER_VERTEX);
10565
+ const indices = new Uint16Array(MAX_SPRITES * INDICES_PER_SPRITE);
10566
+ for (let i = 0; i < MAX_SPRITES; i++) {
10567
+ const v = i * 4;
10568
+ const ii = i * 6;
10569
+ indices[ii] = v;
10570
+ indices[ii + 1] = v + 1;
10571
+ indices[ii + 2] = v + 2;
10572
+ indices[ii + 3] = v;
10573
+ indices[ii + 4] = v + 2;
10574
+ indices[ii + 5] = v + 3;
10575
+ }
10576
+ this.indexBuffer.write(indices);
10577
+ this.projectionMatrix = glMatrix.mat4.create();
10578
+ this.defaultSampler = createLinearSampler(device);
10579
+ }
10580
+ setProjection(width, height) {
10581
+ glMatrix.mat4.ortho(this.projectionMatrix, 0, width, height, 0, -1, 1);
10582
+ this.uniformBuffer.write(this.projectionMatrix);
10583
+ }
10584
+ begin(commandEncoder, renderTarget) {
10585
+ this.currentVertexCount = 0;
10586
+ this.currentIndexCount = 0;
10587
+ this.drawCommands = [];
10588
+ this.currentTexture = null;
10589
+ this._activeEncoder = commandEncoder;
10590
+ this._activeRenderTarget = renderTarget;
10591
+ }
10592
+ drawTexturedQuad(x, y, w, h, texture, u1 = 0, v1 = 0, u2 = 1, v2 = 1, color = [1, 1, 1, 1]) {
10593
+ if (this.currentVertexCount + VERTICES_PER_SPRITE > MAX_SPRITES * VERTICES_PER_SPRITE) {
10594
+ this.flush();
10595
+ }
10596
+ if (this.drawCommands.length === 0 || this.currentTexture !== texture) {
10597
+ this.drawCommands.push({
10598
+ texture,
10599
+ start: this.currentIndexCount,
10600
+ count: 0
10601
+ });
10602
+ this.currentTexture = texture;
10603
+ }
10604
+ const offset = this.currentVertexCount * FLOATS_PER_VERTEX;
10605
+ const [r, g, b, a] = color;
10606
+ this.vertexData[offset] = x;
10607
+ this.vertexData[offset + 1] = y;
10608
+ this.vertexData[offset + 2] = u1;
10609
+ this.vertexData[offset + 3] = v1;
10610
+ this.vertexData[offset + 4] = r;
10611
+ this.vertexData[offset + 5] = g;
10612
+ this.vertexData[offset + 6] = b;
10613
+ this.vertexData[offset + 7] = a;
10614
+ this.vertexData[offset + 8] = x;
10615
+ this.vertexData[offset + 9] = y + h;
10616
+ this.vertexData[offset + 10] = u1;
10617
+ this.vertexData[offset + 11] = v2;
10618
+ this.vertexData[offset + 12] = r;
10619
+ this.vertexData[offset + 13] = g;
10620
+ this.vertexData[offset + 14] = b;
10621
+ this.vertexData[offset + 15] = a;
10622
+ this.vertexData[offset + 16] = x + w;
10623
+ this.vertexData[offset + 17] = y + h;
10624
+ this.vertexData[offset + 18] = u2;
10625
+ this.vertexData[offset + 19] = v2;
10626
+ this.vertexData[offset + 20] = r;
10627
+ this.vertexData[offset + 21] = g;
10628
+ this.vertexData[offset + 22] = b;
10629
+ this.vertexData[offset + 23] = a;
10630
+ this.vertexData[offset + 24] = x + w;
10631
+ this.vertexData[offset + 25] = y;
10632
+ this.vertexData[offset + 26] = u2;
10633
+ this.vertexData[offset + 27] = v1;
10634
+ this.vertexData[offset + 28] = r;
10635
+ this.vertexData[offset + 29] = g;
10636
+ this.vertexData[offset + 30] = b;
10637
+ this.vertexData[offset + 31] = a;
10638
+ this.currentVertexCount += 4;
10639
+ this.currentIndexCount += 6;
10640
+ this.drawCommands[this.drawCommands.length - 1].count += 6;
10641
+ }
10642
+ drawSolidRect(x, y, w, h, color) {
10643
+ if (this.currentTexture !== null) {
10644
+ this.drawCommands.push({
10645
+ texture: null,
10646
+ start: this.currentIndexCount,
10647
+ count: 0
10648
+ });
10649
+ this.currentTexture = null;
10650
+ }
10651
+ this.drawTexturedQuad(x, y, w, h, null, 0, 0, 0, 0, color);
10652
+ }
10653
+ flush() {
10654
+ if (this.currentVertexCount === 0) return;
10655
+ if (!this._activeEncoder || !this._activeRenderTarget) return;
10656
+ const usedFloats = this.currentVertexCount * FLOATS_PER_VERTEX;
10657
+ this.vertexBuffer.write(this.vertexData.subarray(0, usedFloats));
10658
+ const passEncoder = this._activeEncoder.beginRenderPass({
10659
+ colorAttachments: [{
10660
+ view: this._activeRenderTarget,
10661
+ loadOp: "load",
10662
+ storeOp: "store"
10663
+ }],
10664
+ label: "sprite-pass"
10665
+ });
10666
+ passEncoder.setBindGroup(0, this.uniformBindGroup.bindGroup);
10667
+ passEncoder.setVertexBuffer(0, this.vertexBuffer.buffer);
10668
+ passEncoder.setIndexBuffer(this.indexBuffer.buffer, "uint16");
10669
+ for (const cmd of this.drawCommands) {
10670
+ if (cmd.count === 0) continue;
10671
+ if (cmd.texture) {
10672
+ passEncoder.setPipeline(this.pipelineTextured.pipeline);
10673
+ let bindGroup = this.textureBindGroups.get(cmd.texture);
10674
+ if (!bindGroup) {
10675
+ bindGroup = new BindGroup(
10676
+ this.device,
10677
+ this.textureBindGroupLayout,
10678
+ [
10679
+ { binding: 0, resource: this.defaultSampler },
10680
+ { binding: 1, resource: cmd.texture.createView() }
10681
+ ],
10682
+ `sprite-texture-bind-group-${cmd.texture.texture.label}`
10683
+ );
10684
+ this.textureBindGroups.set(cmd.texture, bindGroup);
10685
+ }
10686
+ passEncoder.setBindGroup(1, bindGroup.bindGroup);
10687
+ } else {
10688
+ passEncoder.setPipeline(this.pipelineSolid.pipeline);
10689
+ }
10690
+ passEncoder.drawIndexed(cmd.count, 1, cmd.start, 0, 0);
10691
+ }
10692
+ passEncoder.end();
10693
+ this.currentVertexCount = 0;
10694
+ this.currentIndexCount = 0;
10695
+ this.drawCommands = [];
10696
+ this.currentTexture = null;
10697
+ }
10698
+ end() {
10699
+ this.flush();
10700
+ this._activeEncoder = null;
10701
+ this._activeRenderTarget = null;
10702
+ }
10703
+ /**
10704
+ * Check if sprite renderer is currently in an active render pass
10705
+ * Ref: Defensive check to prevent GPU resource leaks
10706
+ */
10707
+ get isActive() {
10708
+ return this._activeEncoder !== null;
10709
+ }
10710
+ destroy() {
10711
+ this.vertexBuffer.destroy();
10712
+ this.indexBuffer.destroy();
10713
+ this.uniformBuffer.destroy();
10714
+ }
10715
+ };
10716
+
10717
+ // raw-loader:/home/runner/work/quake2/quake2/quake2ts/packages/engine/src/render/webgpu/shaders/skybox.wgsl
10718
+ var skybox_default = "struct Uniforms {\n viewProjection: mat4x4<f32>,\n scroll: vec2<f32>,\n}\n\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var t_skybox: texture_cube<f32>;\n@group(0) @binding(2) var s_skybox: sampler;\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) direction: vec3<f32>,\n}\n\n@vertex\nfn vertexMain(@location(0) position: vec3<f32>) -> VertexOutput {\n var output: VertexOutput;\n\n // Normalize input position (Quake Coordinates)\n var qDir = normalize(position);\n\n // Apply scrolling in Quake Coordinates (Horizontal Plane X/Y)\n // This ensures clouds scroll horizontally regardless of the final mapping.\n qDir.x += uniforms.scroll.x;\n qDir.y += uniforms.scroll.y;\n\n // Transform Quake coordinates (X-Fwd, Y-Left, Z-Up)\n // to WebGPU/GL cubemap coordinates (Right-handed? -Z Fwd, +X Right, +Y Up)\n // Quake X -> GL -Z\n // Quake Y -> GL -X\n // Quake Z -> GL Y\n var dir = vec3<f32>(-qDir.y, qDir.z, -qDir.x);\n\n output.direction = dir;\n\n output.position = uniforms.viewProjection * vec4<f32>(position, 1.0);\n return output;\n}\n\n@fragment\nfn fragmentMain(@location(0) direction: vec3<f32>) -> @location(0) vec4<f32> {\n return textureSample(t_skybox, s_skybox, direction);\n}\n";
10719
+
10720
+ // src/render/types/coordinates.ts
10721
+ var CoordinateSystem = /* @__PURE__ */ ((CoordinateSystem2) => {
10722
+ CoordinateSystem2["QUAKE"] = "quake";
10723
+ CoordinateSystem2["OPENGL"] = "opengl";
10724
+ CoordinateSystem2["WEBGPU"] = "webgpu";
10725
+ return CoordinateSystem2;
10726
+ })(CoordinateSystem || {});
10727
+
10728
+ // src/render/matrix/webgpu.ts
10729
+ var WebGPUMatrixBuilder = class {
10730
+ constructor() {
10731
+ this.coordinateSystem = "webgpu" /* WEBGPU */;
10732
+ }
10733
+ buildProjectionMatrix(camera) {
10734
+ const projection = glMatrix.mat4.create();
10735
+ const f = 1 / Math.tan(camera.fov * shared.DEG2RAD / 2);
10736
+ const rangeInv = 1 / (camera.near - camera.far);
10737
+ projection[0] = f / camera.aspect;
10738
+ projection[5] = f;
10739
+ projection[10] = camera.far * rangeInv;
10740
+ projection[11] = -1;
10741
+ projection[14] = camera.near * camera.far * rangeInv;
10742
+ return projection;
10743
+ }
10744
+ buildViewMatrix(camera) {
10745
+ const [pitch, yaw, roll] = camera.angles;
10746
+ const pitchRad = pitch * shared.DEG2RAD;
10747
+ const yawRad = yaw * shared.DEG2RAD;
10748
+ const rollRad = roll * shared.DEG2RAD;
10749
+ const rotationQuake = glMatrix.mat4.create();
10750
+ glMatrix.mat4.identity(rotationQuake);
10751
+ glMatrix.mat4.rotateZ(rotationQuake, rotationQuake, -yawRad);
10752
+ glMatrix.mat4.rotateY(rotationQuake, rotationQuake, -pitchRad);
10753
+ glMatrix.mat4.rotateX(rotationQuake, rotationQuake, -rollRad);
10754
+ glMatrix.mat4.fromValues(
10755
+ 0,
10756
+ 0,
10757
+ -1,
10758
+ 0,
10759
+ // Column 0: Quake X maps to View Z? Wait.
10760
+ -1,
10761
+ 0,
10762
+ 0,
10763
+ 0,
10764
+ // Column 1: Quake Y maps to View X?
10765
+ 0,
10766
+ 1,
10767
+ 0,
10768
+ 0,
10769
+ // Column 2: Quake Z maps to View Y?
10770
+ 0,
10771
+ 0,
10772
+ 0,
10773
+ 1
10774
+ );
10775
+ const quakeToWgpu = glMatrix.mat4.fromValues(
10776
+ 0,
10777
+ 0,
10778
+ -1,
10779
+ 0,
10780
+ // Col 0: X -> -Z
10781
+ -1,
10782
+ 0,
10783
+ 0,
10784
+ 0,
10785
+ // Col 1: Y -> -X
10786
+ 0,
10787
+ 1,
10788
+ 0,
10789
+ 0,
10790
+ // Col 2: Z -> Y
10791
+ 0,
10792
+ 0,
10793
+ 0,
10794
+ 1
10795
+ );
10796
+ const rotationView = glMatrix.mat4.create();
10797
+ glMatrix.mat4.multiply(rotationView, quakeToWgpu, rotationQuake);
10798
+ const cameraPos = glMatrix.vec3.fromValues(
10799
+ camera.position[0],
10800
+ camera.position[1],
10801
+ camera.position[2]
10802
+ );
10803
+ const t = glMatrix.vec3.transformMat4(glMatrix.vec3.create(), cameraPos, rotationView);
10804
+ glMatrix.vec3.negate(t, t);
10805
+ const view = glMatrix.mat4.clone(rotationView);
10806
+ view[12] = t[0];
10807
+ view[13] = t[1];
10808
+ view[14] = t[2];
10809
+ return view;
10810
+ }
10811
+ };
10812
+ var SKYBOX_POSITIONS2 = new Float32Array([
10813
+ // Front face (+X) - looking forward
10814
+ 1,
10815
+ -1,
10816
+ -1,
10817
+ 1,
10818
+ 1,
10819
+ -1,
10820
+ 1,
10821
+ 1,
10822
+ 1,
10823
+ 1,
10824
+ -1,
10825
+ -1,
10826
+ 1,
10827
+ 1,
10828
+ 1,
10829
+ 1,
10830
+ -1,
10831
+ 1,
10832
+ // Back face (-X) - looking backward
10833
+ -1,
10834
+ 1,
10835
+ -1,
10836
+ -1,
10837
+ -1,
10838
+ -1,
10839
+ -1,
10840
+ -1,
10841
+ 1,
10842
+ -1,
10843
+ 1,
10844
+ -1,
10845
+ -1,
10846
+ -1,
10847
+ 1,
10848
+ -1,
10849
+ 1,
10850
+ 1,
10851
+ // Left face (+Y) - looking left
10852
+ -1,
10853
+ 1,
10854
+ -1,
10855
+ -1,
10856
+ 1,
10857
+ 1,
10858
+ 1,
10859
+ 1,
10860
+ 1,
10861
+ -1,
10862
+ 1,
10863
+ -1,
10864
+ 1,
10865
+ 1,
10866
+ 1,
10867
+ 1,
10868
+ 1,
10869
+ -1,
10870
+ // Right face (-Y) - looking right
10871
+ 1,
10872
+ -1,
10873
+ -1,
10874
+ 1,
10875
+ -1,
10876
+ 1,
10877
+ -1,
10878
+ -1,
10879
+ 1,
10880
+ 1,
10881
+ -1,
10882
+ -1,
10883
+ -1,
10884
+ -1,
10885
+ 1,
10886
+ -1,
10887
+ -1,
10888
+ -1,
10889
+ // Top face (+Z) - looking up
10890
+ -1,
10891
+ -1,
10892
+ 1,
10893
+ 1,
10894
+ -1,
10895
+ 1,
10896
+ 1,
10897
+ 1,
10898
+ 1,
10899
+ -1,
10900
+ -1,
10901
+ 1,
10902
+ 1,
10903
+ 1,
10904
+ 1,
10905
+ -1,
10906
+ 1,
10907
+ 1,
10908
+ // Bottom face (-Z) - looking down
10909
+ -1,
10910
+ 1,
10911
+ -1,
10912
+ 1,
10913
+ 1,
10914
+ -1,
10915
+ 1,
10916
+ -1,
10917
+ -1,
10918
+ -1,
10919
+ 1,
10920
+ -1,
10921
+ 1,
10922
+ -1,
10923
+ -1,
10924
+ -1,
10925
+ -1,
10926
+ -1
10927
+ ]);
10928
+ var SkyboxPipeline3 = class {
10929
+ constructor(device, format) {
10930
+ this.device = device;
10931
+ this.format = format;
10932
+ this.matrixBuilder = new WebGPUMatrixBuilder();
10933
+ const module = device.createShaderModule({
10934
+ label: "skybox-shader",
10935
+ code: skybox_default
10936
+ });
10937
+ this.vertexBuffer = device.createBuffer({
10938
+ label: "skybox-vertex-buffer",
10939
+ size: SKYBOX_POSITIONS2.byteLength,
10940
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
10941
+ mappedAtCreation: true
10942
+ });
10943
+ new Float32Array(this.vertexBuffer.getMappedRange()).set(SKYBOX_POSITIONS2);
10944
+ this.vertexBuffer.unmap();
10945
+ this.uniformBuffer = device.createBuffer({
10946
+ label: "skybox-uniform-buffer",
10947
+ size: 80,
10948
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
10949
+ });
10950
+ this.sampler = device.createSampler({
10951
+ label: "skybox-sampler",
10952
+ minFilter: "linear",
10953
+ magFilter: "linear",
10954
+ addressModeU: "clamp-to-edge",
10955
+ addressModeV: "clamp-to-edge",
10956
+ addressModeW: "clamp-to-edge"
10957
+ });
10958
+ const bindGroupLayout = device.createBindGroupLayout({
10959
+ label: "skybox-bind-group-layout",
10960
+ entries: [
10961
+ {
10962
+ binding: 0,
10963
+ visibility: GPUShaderStage.VERTEX,
10964
+ buffer: { type: "uniform" }
10965
+ },
10966
+ {
10967
+ binding: 1,
10968
+ visibility: GPUShaderStage.FRAGMENT,
10969
+ texture: { viewDimension: "cube" }
10970
+ },
10971
+ {
10972
+ binding: 2,
10973
+ visibility: GPUShaderStage.FRAGMENT,
10974
+ sampler: { type: "filtering" }
10975
+ }
10976
+ ]
10977
+ });
10978
+ this.pipeline = device.createRenderPipeline({
10979
+ label: "skybox-pipeline",
10980
+ layout: device.createPipelineLayout({
10981
+ bindGroupLayouts: [bindGroupLayout]
10982
+ }),
10983
+ vertex: {
10984
+ module,
10985
+ entryPoint: "vertexMain",
10986
+ buffers: [{
10987
+ arrayStride: 12,
10988
+ // vec3<f32>
10989
+ attributes: [{
10990
+ shaderLocation: 0,
10991
+ offset: 0,
10992
+ format: "float32x3"
10993
+ }]
10994
+ }]
10995
+ },
10996
+ fragment: {
10997
+ module,
10998
+ entryPoint: "fragmentMain",
10999
+ targets: [{
11000
+ format: this.format,
11001
+ blend: {
11002
+ // Standard alpha blending (though skybox is usually opaque)
11003
+ color: {
11004
+ srcFactor: "src-alpha",
11005
+ dstFactor: "one-minus-src-alpha",
11006
+ operation: "add"
11007
+ },
11008
+ alpha: {
11009
+ srcFactor: "one",
11010
+ dstFactor: "one-minus-src-alpha",
11011
+ operation: "add"
11012
+ }
11013
+ }
11014
+ }]
11015
+ },
11016
+ depthStencil: {
11017
+ format: "depth24plus",
11018
+ depthWriteEnabled: false,
11019
+ depthCompare: "always"
11020
+ // Skybox is usually drawn first or last. If first, always pass.
11021
+ },
11022
+ primitive: {
11023
+ topology: "triangle-list",
11024
+ cullMode: "none"
11025
+ // Inside the box
11026
+ }
11027
+ });
11028
+ this.bindGroupHelper = new SkyboxBindGroupHelper(device, bindGroupLayout, this.uniformBuffer, this.sampler);
11029
+ }
11030
+ draw(passEncoder, options) {
11031
+ let viewProjection;
11032
+ let useNative = 0;
11033
+ if (options.cameraState) {
11034
+ const view = this.matrixBuilder.buildViewMatrix(options.cameraState);
11035
+ const projection = this.matrixBuilder.buildProjectionMatrix(options.cameraState);
11036
+ view[12] = 0;
11037
+ view[13] = 0;
11038
+ view[14] = 0;
11039
+ const vp = glMatrix.mat4.create();
11040
+ glMatrix.mat4.multiply(vp, projection, view);
11041
+ viewProjection = vp;
11042
+ useNative = 1;
11043
+ } else if (options.viewProjection) {
11044
+ viewProjection = options.viewProjection;
11045
+ useNative = 0;
11046
+ } else {
11047
+ throw new Error("SkyboxPipeline: Either cameraState or viewProjection must be provided");
11048
+ }
11049
+ const uniformData = new Float32Array(20);
11050
+ uniformData.set(viewProjection);
11051
+ uniformData[16] = options.scroll[0];
11052
+ uniformData[17] = options.scroll[1];
11053
+ uniformData[18] = useNative;
11054
+ this.device.queue.writeBuffer(this.uniformBuffer, 0, uniformData);
11055
+ const bindGroup = this.bindGroupHelper.getBindGroup(options.cubemap);
11056
+ passEncoder.setPipeline(this.pipeline);
11057
+ passEncoder.setBindGroup(0, bindGroup);
11058
+ passEncoder.setVertexBuffer(0, this.vertexBuffer);
11059
+ passEncoder.draw(SKYBOX_POSITIONS2.length / 3);
11060
+ }
11061
+ destroy() {
11062
+ this.vertexBuffer.destroy();
11063
+ this.uniformBuffer.destroy();
11064
+ }
11065
+ };
11066
+ var SkyboxBindGroupHelper = class {
11067
+ constructor(device, layout, uniformBuffer, sampler) {
11068
+ this.device = device;
11069
+ this.layout = layout;
11070
+ this.uniformBuffer = uniformBuffer;
11071
+ this.sampler = sampler;
11072
+ this.bindGroupCache = /* @__PURE__ */ new Map();
11073
+ }
11074
+ getBindGroup(cubemap) {
11075
+ if (this.bindGroupCache.has(cubemap)) {
11076
+ return this.bindGroupCache.get(cubemap);
11077
+ }
11078
+ const bindGroup = this.device.createBindGroup({
11079
+ layout: this.layout,
11080
+ entries: [
11081
+ {
11082
+ binding: 0,
11083
+ resource: { buffer: this.uniformBuffer }
11084
+ },
11085
+ {
11086
+ binding: 1,
11087
+ resource: cubemap.createView()
11088
+ },
11089
+ {
11090
+ binding: 2,
11091
+ resource: this.sampler
11092
+ }
11093
+ ]
11094
+ });
11095
+ this.bindGroupCache.set(cubemap, bindGroup);
11096
+ return bindGroup;
11097
+ }
11098
+ };
11099
+
11100
+ // raw-loader:/home/runner/work/quake2/quake2/quake2ts/packages/engine/src/render/webgpu/shaders/bsp.wgsl
11101
+ var bsp_default = "struct DLight {\n position: vec3<f32>,\n intensity: f32,\n color: vec3<f32>,\n padding: f32,\n}\n\nstruct FrameUniforms {\n viewProjection: mat4x4<f32>, // 0-64\n cameraPosition: vec3<f32>, // 64-76\n pad0: f32, // 76-80 (Explicit padding to align next field to 80)\n time: f32, // 80-84\n brightness: f32, // 84-88\n gamma: f32, // 88-92\n ambient: f32, // 92-96\n numDlights: u32, // 96-100\n fullbright: u32, // 100-104\n pad1: vec2<f32>, // 104-112 (Aligns next field to 112)\n pad2: vec4<f32>, // 112-128 (Aligns next field to 128)\n dlights: array<DLight, 32>, // Starts at 128\n}\n\nstruct SurfaceUniforms {\n texScroll: vec2<f32>,\n lightmapScroll: vec2<f32>,\n lightStyleFactors: vec4<f32>,\n styleLayerMapping: vec4<f32>,\n solidColor: vec4<f32>,\n alpha: f32,\n applyLightmap: u32,\n warp: u32,\n lightmapOnly: u32,\n renderMode: u32, // 0: Texture, 1: Solid, 2: Faceted\n padding: vec3<f32>,\n}\n\n@group(0) @binding(0) var<uniform> frame: FrameUniforms;\n@group(1) @binding(0) var<uniform> surface: SurfaceUniforms;\n@group(2) @binding(0) var diffuseMap: texture_2d<f32>;\n@group(2) @binding(1) var diffuseSampler: sampler;\n@group(2) @binding(2) var lightmapAtlas: texture_2d<f32>;\n@group(2) @binding(3) var lightmapSampler: sampler;\n\nstruct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) texCoord: vec2<f32>,\n @location(2) lightmapCoord: vec2<f32>,\n @location(3) lightmapStep: f32,\n}\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) texCoord: vec2<f32>,\n @location(1) lightmapCoord: vec2<f32>,\n @location(2) lightmapStep: f32,\n @location(3) worldPos: vec3<f32>,\n @location(4) screenPos: vec4<f32>,\n}\n\n@vertex\nfn vertexMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n\n var pos = input.position;\n var tex = input.texCoord;\n var lm = input.lightmapCoord;\n\n // Vertex Warping\n if (surface.warp != 0u) {\n let amp = 0.125;\n let s = tex.x + sin((tex.y * 0.125 + frame.time) * 1.0) * amp;\n let t = tex.y + sin((tex.x * 0.125 + frame.time) * 1.0) * amp;\n tex = vec2<f32>(s, t);\n }\n\n output.texCoord = tex + surface.texScroll;\n output.lightmapCoord = lm + surface.lightmapScroll;\n output.lightmapStep = input.lightmapStep;\n output.worldPos = pos;\n output.position = frame.viewProjection * vec4<f32>(pos, 1.0);\n output.screenPos = output.position;\n\n return output;\n}\n\n@fragment\nfn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {\n var finalColor: vec4<f32>;\n\n if (surface.renderMode == 0u) {\n // TEXTURED MODE\n var base = vec4<f32>(1.0, 1.0, 1.0, 1.0);\n if (surface.lightmapOnly == 0u) {\n base = textureSample(diffuseMap, diffuseSampler, input.texCoord);\n }\n\n var totalLight = vec3<f32>(0.0, 0.0, 0.0);\n\n if (frame.fullbright != 0u) {\n totalLight = vec3<f32>(1.0, 1.0, 1.0);\n } else {\n // Apply Lightmaps\n if (surface.applyLightmap != 0u) {\n var hasLight = false;\n for (var i = 0; i < 4; i++) {\n let layer = surface.styleLayerMapping[i];\n let factor = surface.lightStyleFactors[i];\n\n if (layer >= -0.5) {\n let offset = vec2<f32>(0.0, layer * input.lightmapStep);\n totalLight += textureSample(lightmapAtlas, lightmapSampler, input.lightmapCoord + offset).rgb * factor;\n hasLight = true;\n }\n }\n\n // If lightmap enabled but no active layers, fallback to white?\n // Quake 2 logic usually implies at least one style is active if surfaced has SURF_LIGHT\n // But if we have no lightmap data, we start black.\n if (!hasLight) {\n // Fallback to avoid pitch black if lightmap intended but missing?\n // totalLight = vec3<f32>(1.0, 1.0, 1.0);\n }\n }\n\n // Apply Dynamic Lights\n for (var i = 0u; i < 32u; i++) {\n if (i >= frame.numDlights) {\n break;\n }\n let dlight = frame.dlights[i];\n let dist = distance(input.worldPos, dlight.position);\n\n if (dist < dlight.intensity) {\n let contribution = (dlight.intensity - dist) * (1.0 / 255.0);\n totalLight += dlight.color * contribution;\n }\n }\n }\n\n totalLight = max(totalLight, vec3<f32>(frame.ambient));\n totalLight *= frame.brightness;\n base = vec4<f32>(base.rgb * totalLight, base.a);\n\n // Gamma correction\n if (frame.gamma != 1.0) {\n base = vec4<f32>(pow(base.rgb, vec3<f32>(1.0 / frame.gamma)), base.a);\n }\n\n finalColor = vec4<f32>(base.rgb, base.a * surface.alpha);\n\n } else {\n // SOLID / WIREFRAME / FACETED\n var color = surface.solidColor.rgb;\n if (surface.renderMode == 2u) {\n // FACETED\n let fdx = dpdx(input.worldPos);\n let fdy = dpdy(input.worldPos);\n let faceNormal = normalize(cross(fdx, fdy));\n let lightDir = normalize(vec3<f32>(0.5, 0.5, 1.0));\n let diff = max(dot(faceNormal, lightDir), 0.2);\n color *= diff;\n }\n finalColor = vec4<f32>(color, surface.solidColor.a * surface.alpha);\n }\n\n // Alpha Test (Simple discard for fence textures if alpha is very low)\n if (finalColor.a < 0.01) {\n discard;\n }\n\n return finalColor;\n}\n";
11102
+
11103
+ // src/render/webgpu/pipelines/bspPipeline.ts
11104
+ var DEFAULT_STYLE_INDICES2 = [0, 255, 255, 255];
11105
+ var DEFAULT_STYLE_LAYERS2 = [0, -1, -1, -1];
11106
+ function resolveLightStyles2(styleIndices = DEFAULT_STYLE_INDICES2, styleValues = []) {
11107
+ const factors = new Float32Array(4);
11108
+ for (let i = 0; i < 4; i += 1) {
11109
+ const styleIndex = styleIndices[i] ?? 255;
11110
+ if (styleIndex === 255) {
11111
+ factors[i] = 0;
11112
+ continue;
11113
+ }
11114
+ const value = styleValues[styleIndex];
11115
+ factors[i] = value !== void 0 ? value : 1;
11116
+ }
11117
+ return factors;
11118
+ }
11119
+ function computeFlowOffset2(timeSeconds) {
11120
+ const cycle = timeSeconds * 0.25 % 1;
11121
+ return [-cycle, 0];
11122
+ }
11123
+ function deriveSurfaceRenderState2(surfaceFlags = shared.SURF_NONE, timeSeconds = 0) {
11124
+ const flowing = (surfaceFlags & shared.SURF_FLOWING) !== 0;
11125
+ const warp = (surfaceFlags & shared.SURF_WARP) !== 0;
11126
+ const sky = (surfaceFlags & shared.SURF_SKY) !== 0;
11127
+ const trans33 = (surfaceFlags & shared.SURF_TRANS33) !== 0;
11128
+ const trans66 = (surfaceFlags & shared.SURF_TRANS66) !== 0;
11129
+ const alpha = trans33 ? 0.33 : trans66 ? 0.66 : 1;
11130
+ const blend = trans33 || trans66 || warp;
11131
+ const depthWrite = !blend && !sky;
11132
+ const flowOffset = flowing ? computeFlowOffset2(timeSeconds) : [0, 0];
11133
+ return {
11134
+ alpha,
11135
+ blend,
11136
+ depthWrite,
11137
+ warp,
11138
+ flowOffset,
11139
+ sky
11140
+ };
11141
+ }
11142
+ var BspSurfacePipeline3 = class {
11143
+ constructor(device, format, depthFormat) {
11144
+ // Cache for texture bind groups
11145
+ this.textureBindGroupCache = /* @__PURE__ */ new Map();
11146
+ this.device = device;
11147
+ const frameBufferSize = 2048;
11148
+ const surfaceBufferSize = 256;
11149
+ this.frameUniformBuffer = device.createBuffer({
11150
+ size: frameBufferSize,
11151
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
11152
+ });
11153
+ this.surfaceUniformBuffer = device.createBuffer({
11154
+ size: surfaceBufferSize,
11155
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
11156
+ });
11157
+ this.frameBindGroupLayout = device.createBindGroupLayout({
11158
+ entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }]
11159
+ });
11160
+ this.surfaceBindGroupLayout = device.createBindGroupLayout({
11161
+ entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }]
11162
+ });
11163
+ this.textureBindGroupLayout = device.createBindGroupLayout({
11164
+ entries: [
11165
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {} },
11166
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
11167
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: {} },
11168
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: {} }
11169
+ ]
11170
+ });
11171
+ this.defaultSampler = createLinearSampler(device);
11172
+ this.defaultWhiteTexture = new Texture2D2(device, {
11173
+ width: 1,
11174
+ height: 1,
11175
+ format: "rgba8unorm",
11176
+ label: "bsp-default-white"
11177
+ });
11178
+ this.defaultWhiteTexture.upload(new Uint8Array([255, 255, 255, 255]));
11179
+ this.defaultBindGroup = device.createBindGroup({
11180
+ layout: this.textureBindGroupLayout,
11181
+ entries: [
11182
+ { binding: 0, resource: this.defaultWhiteTexture.createView() },
11183
+ { binding: 1, resource: this.defaultSampler.sampler },
11184
+ { binding: 2, resource: this.defaultWhiteTexture.createView() },
11185
+ // Use white texture as lightmap fallback
11186
+ { binding: 3, resource: this.defaultSampler.sampler }
11187
+ ]
11188
+ });
11189
+ this.createPipelines(format, depthFormat);
11190
+ this.frameBindGroup = device.createBindGroup({
11191
+ layout: this.frameBindGroupLayout,
11192
+ entries: [{ binding: 0, resource: { buffer: this.frameUniformBuffer } }]
11193
+ });
11194
+ }
11195
+ createPipelines(format, depthFormat) {
11196
+ const module = this.device.createShaderModule({
11197
+ code: bsp_default
11198
+ });
11199
+ const pipelineLayout = this.device.createPipelineLayout({
11200
+ bindGroupLayouts: [
11201
+ this.frameBindGroupLayout,
11202
+ this.surfaceBindGroupLayout,
11203
+ this.textureBindGroupLayout
11204
+ ]
11205
+ });
11206
+ const vertexState = {
11207
+ module,
11208
+ entryPoint: "vertexMain",
11209
+ buffers: [
11210
+ {
11211
+ // Interleaved buffer: pos(3), tex(2), lm(2), step(1) -> 8 floats -> 32 bytes
11212
+ arrayStride: 32,
11213
+ attributes: [
11214
+ { shaderLocation: 0, offset: 0, format: "float32x3" },
11215
+ // Position
11216
+ { shaderLocation: 1, offset: 12, format: "float32x2" },
11217
+ // TexCoord
11218
+ { shaderLocation: 2, offset: 20, format: "float32x2" },
11219
+ // LightmapCoord
11220
+ { shaderLocation: 3, offset: 28, format: "float32" }
11221
+ // LightmapStep
11222
+ ]
11223
+ }
11224
+ ]
11225
+ };
11226
+ this.pipeline = this.device.createRenderPipeline({
11227
+ layout: pipelineLayout,
11228
+ vertex: vertexState,
11229
+ fragment: {
11230
+ module,
11231
+ entryPoint: "fragmentMain",
11232
+ targets: [{
11233
+ format,
11234
+ blend: {
11235
+ color: {
11236
+ srcFactor: "src-alpha",
11237
+ dstFactor: "one-minus-src-alpha",
11238
+ operation: "add"
11239
+ },
11240
+ alpha: {
11241
+ srcFactor: "one",
11242
+ dstFactor: "one-minus-src-alpha",
11243
+ operation: "add"
11244
+ }
11245
+ }
11246
+ }]
11247
+ },
11248
+ primitive: {
11249
+ topology: "triangle-list",
11250
+ cullMode: "back"
11251
+ // Quake 2 is usually back-face culled
11252
+ },
11253
+ depthStencil: {
11254
+ format: depthFormat,
11255
+ depthWriteEnabled: true,
11256
+ depthCompare: "less"
11257
+ }
11258
+ });
11259
+ this.pipelineWireframe = this.device.createRenderPipeline({
11260
+ layout: pipelineLayout,
11261
+ vertex: vertexState,
11262
+ fragment: {
11263
+ module,
11264
+ entryPoint: "fragmentMain",
11265
+ targets: [{ format, writeMask: GPUColorWrite.ALL }]
11266
+ },
11267
+ primitive: {
11268
+ topology: "line-list"
11269
+ },
11270
+ depthStencil: {
11271
+ format: depthFormat,
11272
+ depthWriteEnabled: true,
11273
+ depthCompare: "less"
11274
+ }
11275
+ });
11276
+ }
11277
+ bind(passEncoder, options) {
11278
+ const {
11279
+ modelViewProjection,
11280
+ styleIndices = DEFAULT_STYLE_INDICES2,
11281
+ styleLayers = DEFAULT_STYLE_LAYERS2,
11282
+ styleValues = [],
11283
+ diffuseTexture,
11284
+ diffuseSampler,
11285
+ lightmapTexture,
11286
+ lightmapSampler,
11287
+ surfaceFlags = shared.SURF_NONE,
11288
+ timeSeconds = 0,
11289
+ texScroll,
11290
+ alpha,
11291
+ warp,
11292
+ dlights = [],
11293
+ renderMode,
11294
+ lightmapOnly,
11295
+ brightness = 1,
11296
+ gamma = 1,
11297
+ fullbright = false,
11298
+ ambient = 0,
11299
+ cameraPosition = [0, 0, 0]
11300
+ } = options;
11301
+ const state = deriveSurfaceRenderState2(surfaceFlags, timeSeconds);
11302
+ const styles = resolveLightStyles2(styleIndices, styleValues);
11303
+ const finalScrollX = texScroll ? texScroll[0] : state.flowOffset[0];
11304
+ const finalScrollY = texScroll ? texScroll[1] : state.flowOffset[1];
11305
+ const finalAlpha = alpha !== void 0 ? alpha : state.alpha;
11306
+ const finalWarp = warp !== void 0 ? warp : state.warp;
11307
+ const frameData = new Float32Array(512);
11308
+ frameData.set(modelViewProjection, 0);
11309
+ frameData.set(cameraPosition, 16);
11310
+ frameData[19] = 0;
11311
+ frameData[20] = timeSeconds;
11312
+ frameData[21] = brightness;
11313
+ frameData[22] = gamma;
11314
+ frameData[23] = ambient;
11315
+ const numDlights = Math.min(dlights.length, MAX_DLIGHTS);
11316
+ const numDlightsView = new Uint32Array(frameData.buffer, 24 * 4, 1);
11317
+ numDlightsView[0] = numDlights;
11318
+ const fullbrightView = new Uint32Array(frameData.buffer, 25 * 4, 1);
11319
+ fullbrightView[0] = fullbright ? 1 : 0;
11320
+ let lightOffset = 32;
11321
+ for (let i = 0; i < numDlights; i++) {
11322
+ const l = dlights[i];
11323
+ frameData[lightOffset + 0] = l.origin.x;
11324
+ frameData[lightOffset + 1] = l.origin.y;
11325
+ frameData[lightOffset + 2] = l.origin.z;
11326
+ frameData[lightOffset + 3] = l.intensity;
11327
+ frameData[lightOffset + 4] = l.color.x;
11328
+ frameData[lightOffset + 5] = l.color.y;
11329
+ frameData[lightOffset + 6] = l.color.z;
11330
+ frameData[lightOffset + 7] = 0;
11331
+ lightOffset += 8;
11332
+ }
11333
+ this.device.queue.writeBuffer(this.frameUniformBuffer, 0, frameData, 0, lightOffset);
11334
+ const surfaceData = new Float32Array(32);
11335
+ surfaceData[0] = finalScrollX;
11336
+ surfaceData[1] = finalScrollY;
11337
+ surfaceData[2] = state.flowOffset[0];
11338
+ surfaceData[3] = state.flowOffset[1];
11339
+ surfaceData.set(styles, 4);
11340
+ surfaceData.set(styleLayers, 8);
11341
+ let modeInt = 0;
11342
+ let color = [1, 1, 1, 1];
11343
+ if (renderMode) {
11344
+ if (renderMode.mode === "solid" || renderMode.mode === "wireframe") {
11345
+ modeInt = 1;
11346
+ } else if (renderMode.mode === "solid-faceted") {
11347
+ modeInt = 2;
11348
+ }
11349
+ if (renderMode.color) {
11350
+ color = [...renderMode.color];
11351
+ } else if (renderMode.generateRandomColor) {
11352
+ color = [1, 1, 1, 1];
11353
+ }
11354
+ }
11355
+ surfaceData.set(color, 12);
11356
+ surfaceData[16] = finalAlpha;
11357
+ const surfaceUint = new Uint32Array(surfaceData.buffer);
11358
+ const applyLightmap = !state.sky && lightmapSampler !== void 0 && !finalWarp;
11359
+ surfaceUint[17] = applyLightmap ? 1 : 0;
11360
+ surfaceUint[18] = finalWarp ? 1 : 0;
11361
+ surfaceUint[19] = lightmapOnly ? 1 : 0;
11362
+ surfaceUint[20] = modeInt;
11363
+ this.device.queue.writeBuffer(this.surfaceUniformBuffer, 0, surfaceData);
11364
+ const surfaceBindGroup = this.device.createBindGroup({
11365
+ layout: this.surfaceBindGroupLayout,
11366
+ entries: [{ binding: 0, resource: { buffer: this.surfaceUniformBuffer } }]
11367
+ });
11368
+ if (diffuseTexture && diffuseSampler && lightmapTexture && lightmapSampler) {
11369
+ const textureBindGroup = this.device.createBindGroup({
11370
+ layout: this.textureBindGroupLayout,
11371
+ entries: [
11372
+ { binding: 0, resource: diffuseTexture },
11373
+ { binding: 1, resource: diffuseSampler },
11374
+ { binding: 2, resource: lightmapTexture },
11375
+ { binding: 3, resource: lightmapSampler }
11376
+ ]
11377
+ });
11378
+ passEncoder.setBindGroup(2, textureBindGroup);
11379
+ } else {
11380
+ passEncoder.setBindGroup(2, this.defaultBindGroup);
11381
+ }
11382
+ const pipeline = renderMode && renderMode.mode === "wireframe" ? this.pipelineWireframe : this.pipeline;
11383
+ passEncoder.setPipeline(pipeline);
11384
+ passEncoder.setBindGroup(0, this.frameBindGroup);
11385
+ passEncoder.setBindGroup(1, surfaceBindGroup);
11386
+ return state;
11387
+ }
11388
+ draw(passEncoder, geometry, renderMode) {
11389
+ if (!geometry.gpuIndexBuffer) return;
11390
+ if (renderMode && renderMode.mode === "wireframe") ; else {
11391
+ passEncoder.setVertexBuffer(0, geometry.gpuVertexBuffer);
11392
+ passEncoder.setIndexBuffer(geometry.gpuIndexBuffer, "uint16");
11393
+ passEncoder.drawIndexed(geometry.indexCount);
11394
+ }
11395
+ }
11396
+ destroy() {
11397
+ this.frameUniformBuffer.destroy();
11398
+ this.surfaceUniformBuffer.destroy();
11399
+ this.defaultWhiteTexture.destroy();
11400
+ }
11401
+ };
11402
+
11403
+ // src/render/webgpu/renderer.ts
11404
+ var WebGPURendererImpl = class {
11405
+ constructor(context, frameRenderer, pipelines) {
11406
+ this.context = context;
11407
+ this.frameRenderer = frameRenderer;
11408
+ this.pipelines = pipelines;
11409
+ this.type = "webgpu";
11410
+ // Texture cache for registered pics
11411
+ this.picCache = /* @__PURE__ */ new Map();
11412
+ this.font = null;
11413
+ // 2D rendering state
11414
+ this.is2DActive = false;
11415
+ this.whiteTexture = new Texture2D2(context.device, {
11416
+ width: 1,
11417
+ height: 1,
11418
+ format: context.format,
11419
+ label: "white-texture"
11420
+ });
11421
+ this.whiteTexture.upload(new Uint8Array([255, 255, 255, 255]));
11422
+ this.collisionVis = null;
11423
+ this.debug = null;
11424
+ this.particleSystem = null;
11425
+ }
11426
+ get device() {
11427
+ return this.context.device;
11428
+ }
11429
+ get width() {
11430
+ return this.context.width;
11431
+ }
11432
+ get height() {
11433
+ return this.context.height;
11434
+ }
11435
+ // =========================================================================
11436
+ // Frame Rendering
11437
+ // =========================================================================
11438
+ renderFrame(options, entities = [], renderOptions) {
11439
+ const localOptions = options;
11440
+ let culledLights = localOptions.dlights;
11441
+ if (localOptions.dlights && localOptions.dlights.length > 0) {
11442
+ const viewProjection = new Float32Array(localOptions.camera.viewProjectionMatrix);
11443
+ const frustumPlanes = extractFrustumPlanes(viewProjection);
11444
+ const cameraPos = { x: localOptions.camera.position[0], y: localOptions.camera.position[1], z: localOptions.camera.position[2] };
11445
+ culledLights = cullLights(
11446
+ localOptions.dlights,
11447
+ frustumPlanes,
11448
+ cameraPos,
11449
+ 32
11450
+ // Max lights
11451
+ );
11452
+ }
11453
+ const augmentedOptions = {
11454
+ ...localOptions,
11455
+ dlights: culledLights
11456
+ };
11457
+ this.frameRenderer.renderFrame(augmentedOptions);
11458
+ }
11459
+ // =========================================================================
11460
+ // Geometry Management
11461
+ // =========================================================================
11462
+ uploadBspGeometry(surfaces) {
11463
+ for (const surface of surfaces) {
11464
+ if (surface.gpuVertexBuffer && surface.gpuIndexBuffer) continue;
11465
+ const vb = new VertexBuffer2(this.device, {
11466
+ size: surface.vertexData.byteLength,
11467
+ label: `bsp-surface-vb-${surface.texture}`
11468
+ });
11469
+ vb.write(surface.vertexData);
11470
+ const ib = new IndexBuffer2(this.device, {
11471
+ size: surface.indexData.byteLength,
11472
+ label: `bsp-surface-ib-${surface.texture}`
11473
+ });
11474
+ ib.write(surface.indexData);
11475
+ const mutableSurface = surface;
11476
+ mutableSurface.gpuVertexBuffer = vb.buffer;
11477
+ mutableSurface.gpuIndexBuffer = ib.buffer;
11478
+ }
11479
+ }
11480
+ // =========================================================================
11481
+ // Texture Management
11482
+ // =========================================================================
11483
+ async registerPic(name, data) {
11484
+ if (this.picCache.has(name)) {
11485
+ return this.picCache.get(name);
11486
+ }
11487
+ const texture = new Texture2D2(this.device, {
11488
+ width: 256,
11489
+ height: 256,
11490
+ format: this.context.format,
11491
+ label: `pic-${name}`
11492
+ });
11493
+ texture.upload(data);
11494
+ this.picCache.set(name, texture);
11495
+ return texture;
11496
+ }
11497
+ registerTexture(name, texture) {
11498
+ if (this.picCache.has(name)) {
11499
+ return this.picCache.get(name);
11500
+ }
11501
+ const gpuTexture = new Texture2D2(this.device, {
11502
+ width: texture.width,
11503
+ height: texture.height,
11504
+ format: this.context.format,
11505
+ mipLevelCount: texture.levels.length,
11506
+ label: `texture-${name}`
11507
+ });
11508
+ for (const mipLevel of texture.levels) {
11509
+ gpuTexture.upload(mipLevel.rgba, {
11510
+ width: mipLevel.width,
11511
+ height: mipLevel.height,
11512
+ mipLevel: mipLevel.level
11513
+ });
11514
+ }
11515
+ this.picCache.set(name, gpuTexture);
11516
+ return gpuTexture;
11517
+ }
11518
+ // =========================================================================
11519
+ // 2D Drawing API
11520
+ // =========================================================================
11521
+ begin2D() {
11522
+ this.frameRenderer.begin2DPass();
11523
+ this.is2DActive = true;
11524
+ }
11525
+ end2D() {
11526
+ this.frameRenderer.end2DPass();
11527
+ this.is2DActive = false;
11528
+ }
11529
+ drawPic(x, y, pic, color = [1, 1, 1, 1]) {
11530
+ if (!this.is2DActive) {
11531
+ throw new Error("drawPic called outside begin2D/end2D");
11532
+ }
11533
+ this.pipelines.sprite.drawTexturedQuad(
11534
+ x,
11535
+ y,
11536
+ pic.width,
11537
+ pic.height,
11538
+ pic,
11539
+ 0,
11540
+ 0,
11541
+ 1,
11542
+ 1,
11543
+ color
11544
+ );
11545
+ }
11546
+ drawfillRect(x, y, width, height, color) {
11547
+ if (!this.is2DActive) {
11548
+ throw new Error("drawfillRect called outside begin2D/end2D");
11549
+ }
11550
+ this.pipelines.sprite.drawSolidRect(x, y, width, height, color);
11551
+ }
11552
+ drawString(x, y, text, color = [1, 1, 1, 1]) {
11553
+ if (!this.is2DActive) {
11554
+ throw new Error("drawString called outside begin2D/end2D");
11555
+ }
11556
+ if (!this.font) {
11557
+ return;
11558
+ }
11559
+ const segments = parseColorString(text);
11560
+ let currentX = x;
11561
+ const charWidth = 8;
11562
+ const charHeight = 8;
11563
+ for (const segment of segments) {
11564
+ const segmentColor = segment.color || color;
11565
+ for (const char of segment.text) {
11566
+ const code = char.charCodeAt(0);
11567
+ const col = code % 16;
11568
+ const row = Math.floor(code / 16);
11569
+ const u0 = col / 16;
11570
+ const v0 = row / 16;
11571
+ const u1 = (col + 1) / 16;
11572
+ const v1 = (row + 1) / 16;
11573
+ this.pipelines.sprite.drawTexturedQuad(
11574
+ currentX,
11575
+ y,
11576
+ charWidth,
11577
+ charHeight,
11578
+ this.font,
11579
+ u0,
11580
+ v0,
11581
+ u1,
11582
+ v1,
11583
+ segmentColor
11584
+ );
11585
+ currentX += charWidth;
11586
+ }
11587
+ }
11588
+ }
11589
+ drawCenterString(y, text) {
11590
+ if (!this.is2DActive) {
11591
+ throw new Error("drawCenterString called outside begin2D/end2D");
11592
+ }
11593
+ const stripped = text.replace(/\^[0-9]/g, "");
11594
+ const charWidth = 8;
11595
+ const width = stripped.length * charWidth;
11596
+ const x = (this.width - width) / 2;
11597
+ this.drawString(x, y, text);
11598
+ }
11599
+ // =========================================================================
11600
+ // Entity Highlighting (Stubs)
11601
+ // =========================================================================
11602
+ setEntityHighlight(entityId, color) {
11603
+ }
11604
+ clearEntityHighlight(entityId) {
11605
+ }
11606
+ highlightSurface(faceIndex, color) {
11607
+ }
11608
+ removeSurfaceHighlight(faceIndex) {
11609
+ }
11610
+ // =========================================================================
11611
+ // Render Settings (Stubs)
11612
+ // =========================================================================
11613
+ setDebugMode(mode) {
11614
+ }
11615
+ setBrightness(value) {
11616
+ }
11617
+ setGamma(value) {
11618
+ }
11619
+ setFullbright(enabled) {
11620
+ }
11621
+ setAmbient(value) {
11622
+ }
11623
+ setLightStyle(index, pattern) {
11624
+ }
11625
+ setUnderwaterWarp(enabled) {
11626
+ }
11627
+ setBloom(enabled) {
11628
+ }
11629
+ setBloomIntensity(value) {
11630
+ }
11631
+ setLodBias(bias) {
11632
+ }
11633
+ // =========================================================================
11634
+ // Instanced Rendering (Stub)
11635
+ // =========================================================================
11636
+ renderInstanced(model, instances) {
11637
+ }
11638
+ // =========================================================================
11639
+ // Performance and Memory
11640
+ // =========================================================================
11641
+ getPerformanceReport() {
11642
+ return {
11643
+ frameTimeMs: 0,
11644
+ gpuTimeMs: 0,
11645
+ cpuFrameTimeMs: 0,
11646
+ drawCalls: 0,
11647
+ triangles: 0,
11648
+ vertices: 0,
11649
+ textureBinds: 0,
11650
+ shaderSwitches: 0,
11651
+ visibleSurfaces: 0,
11652
+ culledSurfaces: 0,
11653
+ visibleEntities: 0,
11654
+ culledEntities: 0,
11655
+ memoryUsageMB: {
11656
+ textures: 0,
11657
+ geometry: 0,
11658
+ total: 0
11659
+ }
11660
+ };
11661
+ }
11662
+ getMemoryUsage() {
11663
+ return {
11664
+ texturesBytes: 0,
11665
+ geometryBytes: 0,
11666
+ shadersBytes: 0,
11667
+ buffersBytes: 0,
11668
+ totalBytes: 0
11669
+ };
11670
+ }
11671
+ // =========================================================================
11672
+ // Lifecycle
11673
+ // =========================================================================
11674
+ resize(width, height) {
11675
+ if (this.context.context && !this.context.isHeadless) {
11676
+ this.context.width = width;
11677
+ this.context.height = height;
11678
+ } else {
11679
+ this.context.width = width;
11680
+ this.context.height = height;
11681
+ }
11682
+ }
11683
+ dispose() {
11684
+ this.pipelines.sprite.destroy();
11685
+ this.pipelines.skybox.destroy();
11686
+ this.pipelines.bsp.destroy();
11687
+ for (const texture of this.picCache.values()) {
11688
+ texture.destroy();
11689
+ }
11690
+ this.picCache.clear();
11691
+ this.whiteTexture.destroy();
11692
+ this.context.device.destroy();
11693
+ }
11694
+ destroy() {
11695
+ this.dispose();
11696
+ }
11697
+ };
11698
+ async function createWebGPURenderer(canvas, options) {
11699
+ const context = await createWebGPUContext(canvas, options);
11700
+ const spriteRenderer = new SpriteRenderer2(context.device, context.format);
11701
+ const skyboxPipeline = new SkyboxPipeline3(context.device, context.format);
11702
+ const bspPipeline = new BspSurfacePipeline3(
11703
+ context.device,
11704
+ context.format,
11705
+ context.depthFormat || "depth24plus"
11706
+ );
11707
+ const pipelines = {
11708
+ sprite: spriteRenderer,
11709
+ skybox: skyboxPipeline,
11710
+ bsp: bspPipeline
11711
+ };
11712
+ if (canvas) {
11713
+ context.width = canvas.width;
11714
+ context.height = canvas.height;
11715
+ } else {
11716
+ context.width = 800;
11717
+ context.height = 600;
11718
+ }
11719
+ const frameRenderer = new FrameRenderer(context, pipelines);
11720
+ return new WebGPURendererImpl(context, frameRenderer, pipelines);
11721
+ }
9617
11722
  var DemoReader = class {
9618
11723
  constructor(buffer) {
9619
11724
  this.messageOffsets = [];
@@ -17430,83 +19535,6 @@ function validateEntity(entity) {
17430
19535
  };
17431
19536
  }
17432
19537
 
17433
- // src/render/webgpu/context.ts
17434
- async function createWebGPUContext(canvas, options) {
17435
- if (!navigator.gpu) {
17436
- throw new Error("WebGPU is not supported in this environment");
17437
- }
17438
- const adapter = await navigator.gpu.requestAdapter({
17439
- powerPreference: options?.powerPreference || "high-performance"
17440
- });
17441
- if (!adapter) {
17442
- throw new Error("No appropriate GPUAdapter found");
17443
- }
17444
- if (options?.requiredFeatures) {
17445
- for (const feature of options.requiredFeatures) {
17446
- if (!adapter.features.has(feature)) {
17447
- throw new Error(`Required feature not available: ${feature}`);
17448
- }
17449
- }
17450
- }
17451
- const deviceDescriptor = {
17452
- requiredFeatures: options?.requiredFeatures,
17453
- requiredLimits: options?.requiredLimits
17454
- };
17455
- const device = await adapter.requestDevice(deviceDescriptor);
17456
- let context;
17457
- let format = "rgba8unorm";
17458
- const depthFormat = "depth24plus";
17459
- let isHeadless = true;
17460
- let width = options?.width || 800;
17461
- let height = options?.height || 600;
17462
- if (canvas) {
17463
- context = canvas.getContext("webgpu");
17464
- if (!context) {
17465
- throw new Error("Failed to get WebGPU context from canvas");
17466
- }
17467
- isHeadless = false;
17468
- format = navigator.gpu.getPreferredCanvasFormat();
17469
- width = canvas.width;
17470
- height = canvas.height;
17471
- context.configure({
17472
- device,
17473
- format,
17474
- alphaMode: "opaque"
17475
- // Standard for game rendering
17476
- });
17477
- }
17478
- const features = /* @__PURE__ */ new Set();
17479
- for (const feature of adapter.features) {
17480
- features.add(feature);
17481
- }
17482
- return {
17483
- adapter,
17484
- device,
17485
- context,
17486
- format,
17487
- depthFormat,
17488
- features,
17489
- limits: device.limits,
17490
- isHeadless,
17491
- width,
17492
- height
17493
- };
17494
- }
17495
- function queryCapabilities(state) {
17496
- const { features, limits } = state;
17497
- return {
17498
- hasTimestampQuery: features.has("timestamp-query"),
17499
- hasDepthClipControl: features.has("depth-clip-control"),
17500
- hasTextureCompressionBC: features.has("texture-compression-bc"),
17501
- hasTextureCompressionETC2: features.has("texture-compression-etc2"),
17502
- hasTextureCompressionASTC: features.has("texture-compression-astc"),
17503
- maxTextureDimension2D: limits.maxTextureDimension2D,
17504
- maxBindGroups: limits.maxBindGroups,
17505
- maxUniformBufferBindingSize: limits.maxUniformBufferBindingSize,
17506
- maxStorageBufferBindingSize: limits.maxStorageBufferBindingSize
17507
- };
17508
- }
17509
-
17510
19538
  // src/render/webgpu/headless.ts
17511
19539
  function createHeadlessRenderTarget(device, width, height, format = "rgba8unorm") {
17512
19540
  const usage = typeof GPUTextureUsage !== "undefined" ? GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC : 17;
@@ -17690,16 +19718,6 @@ var NullRenderer = class {
17690
19718
  return this.frameCount;
17691
19719
  }
17692
19720
  };
17693
-
17694
- // src/render/types/coordinates.ts
17695
- var CoordinateSystem = /* @__PURE__ */ ((CoordinateSystem2) => {
17696
- CoordinateSystem2["QUAKE"] = "quake";
17697
- CoordinateSystem2["OPENGL"] = "opengl";
17698
- CoordinateSystem2["WEBGPU"] = "webgpu";
17699
- return CoordinateSystem2;
17700
- })(CoordinateSystem || {});
17701
-
17702
- // src/render/matrix/webgl.ts
17703
19721
  var WebGLMatrixBuilder = class {
17704
19722
  constructor() {
17705
19723
  this.coordinateSystem = "opengl" /* OPENGL */;
@@ -17766,89 +19784,6 @@ var WebGLMatrixBuilder = class {
17766
19784
  return view;
17767
19785
  }
17768
19786
  };
17769
- var WebGPUMatrixBuilder = class {
17770
- constructor() {
17771
- this.coordinateSystem = "webgpu" /* WEBGPU */;
17772
- }
17773
- buildProjectionMatrix(camera) {
17774
- const projection = glMatrix.mat4.create();
17775
- const f = 1 / Math.tan(camera.fov * shared.DEG2RAD / 2);
17776
- const rangeInv = 1 / (camera.near - camera.far);
17777
- projection[0] = f / camera.aspect;
17778
- projection[5] = f;
17779
- projection[10] = camera.far * rangeInv;
17780
- projection[11] = -1;
17781
- projection[14] = camera.near * camera.far * rangeInv;
17782
- return projection;
17783
- }
17784
- buildViewMatrix(camera) {
17785
- const [pitch, yaw, roll] = camera.angles;
17786
- const pitchRad = pitch * shared.DEG2RAD;
17787
- const yawRad = yaw * shared.DEG2RAD;
17788
- const rollRad = roll * shared.DEG2RAD;
17789
- const rotationQuake = glMatrix.mat4.create();
17790
- glMatrix.mat4.identity(rotationQuake);
17791
- glMatrix.mat4.rotateZ(rotationQuake, rotationQuake, -yawRad);
17792
- glMatrix.mat4.rotateY(rotationQuake, rotationQuake, -pitchRad);
17793
- glMatrix.mat4.rotateX(rotationQuake, rotationQuake, -rollRad);
17794
- glMatrix.mat4.fromValues(
17795
- 0,
17796
- 0,
17797
- -1,
17798
- 0,
17799
- // Column 0: Quake X maps to View Z? Wait.
17800
- -1,
17801
- 0,
17802
- 0,
17803
- 0,
17804
- // Column 1: Quake Y maps to View X?
17805
- 0,
17806
- 1,
17807
- 0,
17808
- 0,
17809
- // Column 2: Quake Z maps to View Y?
17810
- 0,
17811
- 0,
17812
- 0,
17813
- 1
17814
- );
17815
- const quakeToWgpu = glMatrix.mat4.fromValues(
17816
- 0,
17817
- 0,
17818
- -1,
17819
- 0,
17820
- // Col 0: X -> -Z
17821
- -1,
17822
- 0,
17823
- 0,
17824
- 0,
17825
- // Col 1: Y -> -X
17826
- 0,
17827
- 1,
17828
- 0,
17829
- 0,
17830
- // Col 2: Z -> Y
17831
- 0,
17832
- 0,
17833
- 0,
17834
- 1
17835
- );
17836
- const rotationView = glMatrix.mat4.create();
17837
- glMatrix.mat4.multiply(rotationView, quakeToWgpu, rotationQuake);
17838
- const cameraPos = glMatrix.vec3.fromValues(
17839
- camera.position[0],
17840
- camera.position[1],
17841
- camera.position[2]
17842
- );
17843
- const t = glMatrix.vec3.transformMat4(glMatrix.vec3.create(), cameraPos, rotationView);
17844
- glMatrix.vec3.negate(t, t);
17845
- const view = glMatrix.mat4.clone(rotationView);
17846
- view[12] = t[0];
17847
- view[13] = t[1];
17848
- view[14] = t[2];
17849
- return view;
17850
- }
17851
- };
17852
19787
  var IdentityMatrixBuilder = class {
17853
19788
  constructor() {
17854
19789
  this.coordinateSystem = "quake" /* QUAKE */;
@@ -18668,6 +20603,7 @@ exports.createProgramFromSources = createProgramFromSources;
18668
20603
  exports.createRenderer = createRenderer;
18669
20604
  exports.createWebGLContext = createWebGLContext;
18670
20605
  exports.createWebGPUContext = createWebGPUContext;
20606
+ exports.createWebGPURenderer = createWebGPURenderer;
18671
20607
  exports.decodeOgg = decodeOgg;
18672
20608
  exports.deriveSurfaceRenderState = deriveSurfaceRenderState;
18673
20609
  exports.detectFileType = detectFileType;