From 14aba6b12934f7391530e63652de76e4df39b493 Mon Sep 17 00:00:00 2001 From: Paula Date: Thu, 24 Sep 2026 18:41:52 -0300 Subject: [PATCH] Webgpu renderer --- client/components/dimension.ts | 12 +- client/main.ts | 2 +- client/renderer/core.ts | 540 ++++++++++++++++++-------- client/renderer/textures.ts | 33 +- client/renderer/types.ts | 2 +- client/systems/rendering/dimension.ts | 20 +- deno.json | 2 +- 7 files changed, 398 insertions(+), 213 deletions(-) diff --git a/client/components/dimension.ts b/client/components/dimension.ts index 57bf093..d4c077f 100644 --- a/client/components/dimension.ts +++ b/client/components/dimension.ts @@ -7,7 +7,7 @@ import { ClientWorld } from "../client_world.ts"; import { generate_chunk } from "../generation.ts"; import { ItemStack } from "../inventory.ts"; import { PlayerComponent } from "../player.ts"; -import { gl, Texture } from "../renderer/mod.ts"; +import { destroy_vertex_buffer, Texture } from "../renderer/mod.ts"; import { Camera } from "./camera.ts"; export interface BlockData { @@ -34,9 +34,9 @@ export interface Chunk { blocks_data: BlockData[]; generated: boolean; dirty: boolean; - opaque_vertex_buffer?: WebGLBuffer; + opaque_vertex_buffer?: GPUBuffer; opaque_vertex_count?: number; - transparent_vertex_buffer?: WebGLBuffer; + transparent_vertex_buffer?: GPUBuffer; transparent_vertex_count?: number; } @@ -296,10 +296,12 @@ export class Dimension extends Component { delete_chunk_mesh(chunk: Chunk) { if (chunk.opaque_vertex_buffer) { - gl.deleteBuffer(chunk.opaque_vertex_buffer); + destroy_vertex_buffer(chunk.opaque_vertex_buffer); + chunk.opaque_vertex_buffer = undefined; } if (chunk.transparent_vertex_buffer) { - gl.deleteBuffer(chunk.transparent_vertex_buffer); + destroy_vertex_buffer(chunk.transparent_vertex_buffer); + chunk.transparent_vertex_buffer = undefined; } } diff --git a/client/main.ts b/client/main.ts index a519611..aa97f9c 100644 --- a/client/main.ts +++ b/client/main.ts @@ -71,7 +71,7 @@ if (!canvas) { throw Error("Canvas was not found"); } -init_window(canvas); +await init_window(canvas); InputManager.initialize(canvas); diff --git a/client/renderer/core.ts b/client/renderer/core.ts index e38e18b..efe69fc 100644 --- a/client/renderer/core.ts +++ b/client/renderer/core.ts @@ -1,28 +1,44 @@ import { Camera } from "../components/camera.ts"; import { mat4 } from "gl-matrix"; -export let gl: WebGLRenderingContext; +export let device: GPUDevice; export let canvas: HTMLCanvasElement; const MAX_SPRITES = 100000; const VERTS_PER_SPRITE = 6; const FLOATS_PER_VERT = 9; +const VERTEX_STRIDE = FLOATS_PER_VERT * 4; -let program: WebGLProgram; -let main_buffer: WebGLBuffer; +const SAMPLE_COUNT = 4; +const DEPTH_FORMAT: GPUTextureFormat = "depth24plus"; + +// dynamic uniform offsets have to be 256 aligned +const UNIFORM_SLOT_SIZE = 256; + +let context: GPUCanvasContext; +let canvas_format: GPUTextureFormat; + +let pipeline_2d: GPURenderPipeline; +let pipeline_3d: GPURenderPipeline; +let uniform_layout: GPUBindGroupLayout; +let texture_layout: GPUBindGroupLayout; +let sampler: GPUSampler; const vertex_data = new Float32Array(MAX_SPRITES * VERTS_PER_SPRITE * FLOATS_PER_VERT); let vert_index = 0; -let pos_loc: GLint; -let uv_loc: GLint; -let color_loc: GLint; -let texture_loc: WebGLUniformLocation | null; -let mvp_loc: WebGLUniformLocation | null; -let col_diffuse_loc: WebGLUniformLocation | null; +// every flush in a frame appends to this, so earlier draws dont get overwritten before the submit +let stream_buffer: GPUBuffer; +let stream_offset = 0; -let current_texture: WebGLTexture | null = null; -export let white_tex: WebGLTexture | null = null; +let uniform_buffer: GPUBuffer; +let uniform_bind_group: GPUBindGroup; +let uniform_slot = -1; + +const texture_bind_groups = new WeakMap(); + +let current_texture: GPUTexture | null = null; +export let white_tex: GPUTexture | null = null; let mode3d = false; @@ -33,91 +49,124 @@ const proj = mat4.create(); const view = mat4.create(); const mvp = mat4.create(); -const vertex_src = `#version 300 es -precision mediump float; -in vec3 vertexPosition; -in vec2 vertexTexCoord; -in vec4 vertexColor; -out vec2 fragTexCoord; -out vec4 fragColor; -uniform mat4 mvp; +// per frame state +let encoder: GPUCommandEncoder | undefined; +let pass: GPURenderPassEncoder | undefined; +let frame_view: GPUTextureView; +let msaa_texture: GPUTexture | undefined; +let depth_texture: GPUTexture | undefined; +let current_pipeline: GPURenderPipeline | undefined; +let clear_color: GPUColor = { r: 0, g: 0, b: 0, a: 1 }; +let pending_color_clear = false; +let pending_depth_clear = false; +let scissor: { x: number; y: number; width: number; height: number } | undefined; +let pending_destroy: GPUBuffer[] = []; -void main() { - fragTexCoord = vertexTexCoord; - fragColor = vertexColor; - gl_Position = mvp*vec4(vertexPosition, 1.0); +const shader_src = /* wgsl */ ` +struct Uniforms { + mvp: mat4x4, +} + +@group(0) @binding(0) var uniforms: Uniforms; +@group(1) @binding(0) var texture0: texture_2d; +@group(1) @binding(1) var sampler0: sampler; + +struct VertexOut { + @builtin(position) position: vec4, + @location(0) tex_coord: vec2, + @location(1) color: vec4, +} + +@vertex +fn vs_main( + @location(0) position: vec3, + @location(1) tex_coord: vec2, + @location(2) color: vec4, +) -> VertexOut { + var out: VertexOut; + out.position = uniforms.mvp * vec4(position, 1.0); + out.tex_coord = tex_coord; + out.color = color; + return out; +} + +@fragment +fn fs_main(in: VertexOut) -> @location(0) vec4 { + return textureSample(texture0, sampler0, in.tex_coord) * in.color; } `; -const fragment_src = `#version 300 es -precision mediump float; -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; -void main() { - vec4 texelColor = texture(texture0, fragTexCoord); - finalColor = texelColor*colDiffuse*fragColor; -} -`; - -export function init_window(canvas_element: HTMLCanvasElement) { +export async function init_window(canvas_element: HTMLCanvasElement) { canvas = canvas_element; canvas.width = 1800; canvas.height = 900; - const ctx = canvas.getContext("webgl2", { antialias: true, alpha: false }); - if (!ctx) { - throw new Error("WebGL2 not supported"); + if (!navigator.gpu) { + throw new Error("WebGPU not supported"); } + const adapter = await navigator.gpu.requestAdapter(); + if (!adapter) { + throw new Error("No WebGPU adapter found"); + } + device = await adapter.requestDevice(); + device.addEventListener("uncapturederror", (event) => { + console.error("WebGPU error:", (event as GPUUncapturedErrorEvent).error.message); + }); + device.lost.then((info) => console.error(`WebGPU device lost: ${info.message}`)); - gl = ctx; + // the dom typings dont know about the webgpu overload yet + const ctx = canvas.getContext("webgpu") as GPUCanvasContext | null; + if (!ctx) { + throw new Error("WebGPU not supported"); + } + context = ctx; + canvas_format = navigator.gpu.getPreferredCanvasFormat(); + context.configure({ device, format: canvas_format, alphaMode: "opaque" }); - program = create_program(vertex_src, fragment_src); + create_pipelines(); - main_buffer = gl.createBuffer()!; - gl.bindBuffer(gl.ARRAY_BUFFER, main_buffer); + sampler = device.createSampler({ + magFilter: "nearest", + minFilter: "nearest", + addressModeU: "clamp-to-edge", + addressModeV: "clamp-to-edge", + }); - const stride = FLOATS_PER_VERT * 4; - - pos_loc = gl.getAttribLocation(program, "vertexPosition"); - gl.enableVertexAttribArray(pos_loc); - gl.vertexAttribPointer(pos_loc, 3, gl.FLOAT, false, stride, 0); - - uv_loc = gl.getAttribLocation(program, "vertexTexCoord"); - gl.enableVertexAttribArray(uv_loc); - gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 12); - - color_loc = gl.getAttribLocation(program, "vertexColor"); - gl.enableVertexAttribArray(color_loc); - gl.vertexAttribPointer(color_loc, 4, gl.FLOAT, false, stride, 20); - - mvp_loc = gl.getUniformLocation(program, "mvp"); - texture_loc = gl.getUniformLocation(program, "texture0"); - col_diffuse_loc = gl.getUniformLocation(program, "colDiffuse"); - - gl.useProgram(program); - - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - - gl.cullFace(gl.BACK); - gl.frontFace(gl.CCW); + stream_buffer = create_stream_buffer(8 * 1024 * 1024); + create_uniform_buffer(64); create_white_texture(); } export function begin_drawing() { + encoder = device.createCommandEncoder(); + frame_view = context.getCurrentTexture().createView(); + ensure_render_targets(); + update_2d_mvp(); - gl.depthFunc(gl.LEQUAL); - gl.clearDepth(1.0); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + stream_offset = 0; + uniform_slot = -1; + write_mvp(); + + pending_color_clear = true; + pending_depth_clear = true; vert_index = 0; } export function end_drawing() { flush_batch(); + // make sure the frame gets cleared even if nothing was drawn + ensure_pass(); + + pass!.end(); + pass = undefined; + device.queue.submit([encoder!.finish()]); + encoder = undefined; + + for (const buffer of pending_destroy) { + buffer.destroy(); + } + pending_destroy = []; } function update_2d_mvp() { @@ -131,19 +180,23 @@ function update_2d_mvp() { const n = -1; const f = 1; - mat4.ortho(ortho, l, r, b, t, n, f); + mat4.orthoZO(ortho, l, r, b, t, n, f); } export function begin_clip(x: number, y: number, width: number, height: number) { - gl.enable(gl.SCISSOR_TEST); - - const flipped_y = canvas.height - (y + height); - - gl.scissor(x, flipped_y, width, height); + flush_batch(); + scissor = { x, y, width, height }; + if (pass) { + apply_scissor(pass); + } } export function end_clip() { - gl.disable(gl.SCISSOR_TEST); + flush_batch(); + scissor = undefined; + if (pass) { + apply_scissor(pass); + } } export function begin_mode_3d(new_camera: Camera) { @@ -152,13 +205,8 @@ export function begin_mode_3d(new_camera: Camera) { mode3d = true; camera = new_camera; - gl.enable(gl.DEPTH_TEST); - gl.enable(gl.CULL_FACE); - gl.depthMask(true); - gl.enable(gl.POLYGON_OFFSET_FILL); - gl.polygonOffset(1, 1); - update_camera(); + write_mvp(); } export function end_mode_3d() { @@ -169,15 +217,18 @@ export function end_mode_3d() { flush_batch(); mode3d = false; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.depthMask(false); - gl.disable(gl.POLYGON_OFFSET_FILL); + write_mvp(); } export function clear_background(r: number, g: number, b: number, a = 1) { - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT); + flush_batch(); + clear_color = { r, g, b, a }; + // the clear happens when a render pass starts, so start a new one + if (pass) { + pass.end(); + pass = undefined; + } + pending_color_clear = true; } export function flush_batch() { @@ -185,52 +236,41 @@ export function flush_batch() { return; } - gl.bindBuffer(gl.ARRAY_BUFFER, main_buffer); - gl.bufferData(gl.ARRAY_BUFFER, vertex_data.subarray(0, vert_index), gl.STREAM_DRAW); - - if (mode3d) { - gl.uniformMatrix4fv(mvp_loc, false, mvp); - } else { - gl.uniformMatrix4fv(mvp_loc, false, ortho); + const byte_length = vert_index * 4; + if (stream_offset + byte_length > stream_buffer.size) { + // the old buffer might still be used by draws in this frame, destroy it after the submit + pending_destroy.push(stream_buffer); + stream_buffer = create_stream_buffer(Math.max(stream_buffer.size * 2, byte_length)); + stream_offset = 0; } - const stride = FLOATS_PER_VERT * 4; - gl.vertexAttribPointer(pos_loc, 3, gl.FLOAT, false, stride, 0); - gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 12); - gl.vertexAttribPointer(color_loc, 4, gl.FLOAT, false, stride, 20); - - gl.uniform4f(col_diffuse_loc, 1, 1, 1, 1); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, current_texture); - gl.uniform1i(texture_loc, 0); - - gl.drawArrays(gl.TRIANGLES, 0, vert_index / FLOATS_PER_VERT); + device.queue.writeBuffer(stream_buffer, stream_offset, vertex_data, 0, vert_index); + draw(stream_buffer, stream_offset, vert_index / FLOATS_PER_VERT); + stream_offset += byte_length; vert_index = 0; } -export function flush_buffer(buffer: WebGLBuffer, draw_count: number) { - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); +export function flush_buffer(buffer: GPUBuffer, draw_count: number) { + draw(buffer, 0, draw_count); +} - if (mode3d) { - gl.uniformMatrix4fv(mvp_loc, false, mvp); +export function create_vertex_buffer(vertices: Float32Array): GPUBuffer { + const buffer = device.createBuffer({ + size: Math.max(4, vertices.byteLength), + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, + }); + device.queue.writeBuffer(buffer, 0, vertices); + return buffer; +} + +export function destroy_vertex_buffer(buffer: GPUBuffer) { + // it might be used by a draw thats not submitted yet + if (encoder) { + pending_destroy.push(buffer); } else { - gl.uniformMatrix4fv(mvp_loc, false, ortho); + buffer.destroy(); } - - const stride = FLOATS_PER_VERT * 4; - gl.vertexAttribPointer(pos_loc, 3, gl.FLOAT, false, stride, 0); - gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 12); - gl.vertexAttribPointer(color_loc, 4, gl.FLOAT, false, stride, 20); - - gl.uniform4f(col_diffuse_loc, 1, 1, 1, 1); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, current_texture); - gl.uniform1i(texture_loc, 0); - - gl.drawArrays(gl.TRIANGLES, 0, draw_count); } export function resize_canvas() { @@ -242,21 +282,26 @@ export function resize_canvas() { canvas.height = height; canvas.style.width = canvas.width + "px"; canvas.style.height = canvas.height + "px"; - - gl.viewport(0, 0, width, height); - - gl.useProgram(program); + // render targets get recreated at the start of the next frame } } -export function get_current_texture(): WebGLTexture | null { +export function get_current_texture(): GPUTexture | null { return current_texture; } -export function set_current_texture(texture: WebGLTexture) { +export function set_current_texture(texture: GPUTexture) { current_texture = texture; } +export function create_texture(width: number, height: number): GPUTexture { + return device.createTexture({ + size: [width, height], + format: "rgba8unorm", + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, + }); +} + export function push_vertex(px: number, py: number, pz: number, u: number, vv: number, r = 1, g = 1, b = 1, a = 1) { let i = vert_index; vertex_data[i++] = px; @@ -330,12 +375,115 @@ export function push_quad_vertices( // internal +function draw(buffer: GPUBuffer, offset: number, vertex_count: number) { + if (!current_texture || vertex_count === 0) { + return; + } + + const render_pass = ensure_pass(); + + const pipeline = mode3d ? pipeline_3d : pipeline_2d; + if (current_pipeline !== pipeline) { + render_pass.setPipeline(pipeline); + current_pipeline = pipeline; + } + + render_pass.setBindGroup(0, uniform_bind_group, [uniform_slot * UNIFORM_SLOT_SIZE]); + render_pass.setBindGroup(1, get_texture_bind_group(current_texture)); + render_pass.setVertexBuffer(0, buffer, offset); + render_pass.draw(vertex_count); +} + +function ensure_pass(): GPURenderPassEncoder { + if (pass) { + return pass; + } + + pass = encoder!.beginRenderPass({ + colorAttachments: [{ + view: msaa_texture!.createView(), + resolveTarget: frame_view, + clearValue: clear_color, + loadOp: pending_color_clear ? "clear" : "load", + storeOp: "store", + }], + depthStencilAttachment: { + view: depth_texture!.createView(), + depthClearValue: 1, + depthLoadOp: pending_depth_clear ? "clear" : "load", + depthStoreOp: "store", + }, + }); + pending_color_clear = false; + pending_depth_clear = false; + current_pipeline = undefined; + apply_scissor(pass); + + return pass; +} + +function apply_scissor(render_pass: GPURenderPassEncoder) { + const target_width = msaa_texture!.width; + const target_height = msaa_texture!.height; + + if (!scissor) { + render_pass.setScissorRect(0, 0, target_width, target_height); + return; + } + + // webgpu errors on rects outside the target instead of clipping them + const x0 = Math.min(target_width, Math.max(0, Math.floor(scissor.x))); + const y0 = Math.min(target_height, Math.max(0, Math.floor(scissor.y))); + const x1 = Math.min(target_width, Math.max(x0, Math.ceil(scissor.x + scissor.width))); + const y1 = Math.min(target_height, Math.max(y0, Math.ceil(scissor.y + scissor.height))); + + render_pass.setScissorRect(x0, y0, x1 - x0, y1 - y0); +} + +function ensure_render_targets() { + const width = canvas.width; + const height = canvas.height; + + if (msaa_texture && msaa_texture.width === width && msaa_texture.height === height) { + return; + } + + msaa_texture?.destroy(); + depth_texture?.destroy(); + + msaa_texture = device.createTexture({ + size: [width, height], + format: canvas_format, + sampleCount: SAMPLE_COUNT, + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + depth_texture = device.createTexture({ + size: [width, height], + format: DEPTH_FORMAT, + sampleCount: SAMPLE_COUNT, + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); +} + +// puts the current matrix in a new uniform slot, earlier draws in the frame keep using theirs +function write_mvp() { + uniform_slot += 1; + if (uniform_slot * UNIFORM_SLOT_SIZE >= uniform_buffer.size) { + pending_destroy.push(uniform_buffer); + create_uniform_buffer((uniform_buffer.size / UNIFORM_SLOT_SIZE) * 2); + uniform_slot = 0; + } + + const matrix = mode3d ? mvp : ortho; + device.queue.writeBuffer(uniform_buffer, uniform_slot * UNIFORM_SLOT_SIZE, matrix as Float32Array); +} + function update_camera() { if (!camera) { return; } - mat4.perspective( + mat4.perspectiveZO( proj, camera.fov, canvas.width / canvas.height, @@ -354,47 +502,107 @@ function update_camera() { mat4.multiply(mvp, proj, view); } -function compile_shader(type: number, src: string) { - const shader = gl.createShader(type)!; +function create_pipelines() { + const module = device.createShaderModule({ code: shader_src }); - gl.shaderSource(shader, src); - gl.compileShader(shader); + uniform_layout = device.createBindGroupLayout({ + entries: [{ + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 64 }, + }], + }); + texture_layout = device.createBindGroupLayout({ + entries: [ + { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {} }, + { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} }, + ], + }); + const layout = device.createPipelineLayout({ bindGroupLayouts: [uniform_layout, texture_layout] }); - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - console.error(gl.getShaderInfoLog(shader)); - throw new Error("Shader compile failed"); - } + const vertex: GPUVertexState = { + module, + entryPoint: "vs_main", + buffers: [{ + arrayStride: VERTEX_STRIDE, + attributes: [ + { shaderLocation: 0, offset: 0, format: "float32x3" }, + { shaderLocation: 1, offset: 12, format: "float32x2" }, + { shaderLocation: 2, offset: 20, format: "float32x4" }, + ], + }], + }; + const fragment: GPUFragmentState = { + module, + entryPoint: "fs_main", + targets: [{ + format: canvas_format, + blend: { + color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" }, + alpha: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" }, + }, + }], + }; + const multisample: GPUMultisampleState = { count: SAMPLE_COUNT }; - return shader; + pipeline_2d = device.createRenderPipeline({ + layout, + vertex, + fragment, + multisample, + primitive: { topology: "triangle-list", cullMode: "none" }, + depthStencil: { format: DEPTH_FORMAT, depthWriteEnabled: false, depthCompare: "always" }, + }); + + pipeline_3d = device.createRenderPipeline({ + layout, + vertex, + fragment, + multisample, + primitive: { topology: "triangle-list", cullMode: "back", frontFace: "ccw" }, + depthStencil: { + format: DEPTH_FORMAT, + depthWriteEnabled: true, + depthCompare: "less-equal", + // same as the old polygonOffset(1, 1) + depthBias: 1, + depthBiasSlopeScale: 1, + }, + }); } -function create_program(vs_src: string, fs_src: string) { - const vs = compile_shader(gl.VERTEX_SHADER, vs_src); - const fs = compile_shader(gl.FRAGMENT_SHADER, fs_src); +function create_stream_buffer(size: number) { + return device.createBuffer({ size, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST }); +} - const program = gl.createProgram()!; - gl.attachShader(program, vs); - gl.attachShader(program, fs); - gl.linkProgram(program); +function create_uniform_buffer(slots: number) { + uniform_buffer = device.createBuffer({ + size: slots * UNIFORM_SLOT_SIZE, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + uniform_bind_group = device.createBindGroup({ + layout: uniform_layout, + entries: [{ binding: 0, resource: { buffer: uniform_buffer, size: 64 } }], + }); +} - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - console.error(gl.getProgramInfoLog(program)); - throw new Error("Program link failed"); +function get_texture_bind_group(texture: GPUTexture) { + let bind_group = texture_bind_groups.get(texture); + if (!bind_group) { + bind_group = device.createBindGroup({ + layout: texture_layout, + entries: [ + { binding: 0, resource: texture.createView() }, + { binding: 1, resource: sampler }, + ], + }); + texture_bind_groups.set(texture, bind_group); } - - return program; + return bind_group; } function create_white_texture() { - const tex = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, tex); - const white_pixel = new Uint8Array([255, 255, 255, 255]); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, white_pixel); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - + const tex = create_texture(1, 1); + device.queue.writeTexture({ texture: tex }, new Uint8Array([255, 255, 255, 255]), { bytesPerRow: 4 }, [1, 1]); white_tex = tex; } diff --git a/client/renderer/textures.ts b/client/renderer/textures.ts index f46898a..60b89f3 100644 --- a/client/renderer/textures.ts +++ b/client/renderer/textures.ts @@ -1,28 +1,17 @@ -import { flush_batch, get_current_texture, gl, push_quad, push_quad_vertices, set_current_texture } from "./core.ts"; +import { + create_texture, + device, + flush_batch, + get_current_texture, + push_quad, + push_quad_vertices, + set_current_texture, +} from "./core.ts"; import { Texture } from "./types.ts"; export function load_texture(image: HTMLImageElement): Texture { - const texture = gl.createTexture(); - - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - - gl.texImage2D( - gl.TEXTURE_2D, - 0, - gl.RGBA, - gl.RGBA, - gl.UNSIGNED_BYTE, - image, - ); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + const texture = create_texture(image.width, image.height); + device.queue.copyExternalImageToTexture({ source: image }, { texture }, [image.width, image.height]); return { tex: texture, diff --git a/client/renderer/types.ts b/client/renderer/types.ts index e4875e6..1e0c3f0 100644 --- a/client/renderer/types.ts +++ b/client/renderer/types.ts @@ -1,5 +1,5 @@ export interface Texture { - tex: WebGLTexture; + tex: GPUTexture; width: number; height: number; } diff --git a/client/systems/rendering/dimension.ts b/client/systems/rendering/dimension.ts index e313184..35e3e48 100644 --- a/client/systems/rendering/dimension.ts +++ b/client/systems/rendering/dimension.ts @@ -2,7 +2,7 @@ import { EverythingRegistry } from "$/common/everything_registry.ts"; import { Chunk, Dimension } from "$/client/components/dimension.ts"; import { Camera } from "$/client/components/camera.ts"; import { AssetManager } from "$/client/assets.ts"; -import { flush_buffer, gl, set_current_texture } from "$/client/renderer/mod.ts"; +import { create_vertex_buffer, flush_buffer, set_current_texture } from "$/client/renderer/mod.ts"; let gdimension: Dimension; @@ -20,25 +20,11 @@ worker.onmessage = (event) => { gdimension.delete_chunk_mesh(chunk); chunk.dirty = false; - chunk.opaque_vertex_buffer = gl.createBuffer(); + chunk.opaque_vertex_buffer = create_vertex_buffer(opaque_vertices.subarray(0, opaque_count)); chunk.opaque_vertex_count = opaque_count / 9; - gl.bindBuffer(gl.ARRAY_BUFFER, chunk.opaque_vertex_buffer); - gl.bufferData( - gl.ARRAY_BUFFER, - opaque_vertices.subarray(0, opaque_count), - gl.STATIC_DRAW, - ); - - chunk.transparent_vertex_buffer = gl.createBuffer(); + chunk.transparent_vertex_buffer = create_vertex_buffer(transparent_vertices.subarray(0, transparent_count)); chunk.transparent_vertex_count = transparent_count / 9; - - gl.bindBuffer(gl.ARRAY_BUFFER, chunk.transparent_vertex_buffer); - gl.bufferData( - gl.ARRAY_BUFFER, - transparent_vertices.subarray(0, transparent_count), - gl.STATIC_DRAW, - ); }; function strip_functions>(obj: T) { diff --git a/deno.json b/deno.json index 2fe818d..5ecf429 100644 --- a/deno.json +++ b/deno.json @@ -5,7 +5,7 @@ "server": "deno run --allow-net --allow-read --allow-write --allow-env server/main.ts" }, "compilerOptions": { - "lib": ["dom", "dom.asynciterable", "dom.iterable", "deno.ns", "deno.unstable"] + "lib": ["dom", "dom.asynciterable", "dom.iterable", "deno.ns", "deno.unstable", "deno.webgpu"] }, "unstable": ["bundle", "raw-imports"], "fmt": {