Webgpu renderer

This commit is contained in:
2026-09-24 18:41:52 -03:00
parent a37ffd9127
commit 14aba6b129
7 changed files with 398 additions and 213 deletions
+7 -5
View File
@@ -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<T = unknown> {
@@ -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;
}
}
+1 -1
View File
@@ -71,7 +71,7 @@ if (!canvas) {
throw Error("Canvas was not found");
}
init_window(canvas);
await init_window(canvas);
InputManager.initialize(canvas);
+374 -166
View File
@@ -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<GPUTexture, GPUBindGroup>();
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<f32>,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(1) @binding(0) var texture0: texture_2d<f32>;
@group(1) @binding(1) var sampler0: sampler;
struct VertexOut {
@builtin(position) position: vec4<f32>,
@location(0) tex_coord: vec2<f32>,
@location(1) color: vec4<f32>,
}
@vertex
fn vs_main(
@location(0) position: vec3<f32>,
@location(1) tex_coord: vec2<f32>,
@location(2) color: vec4<f32>,
) -> VertexOut {
var out: VertexOut;
out.position = uniforms.mvp * vec4<f32>(position, 1.0);
out.tex_coord = tex_coord;
out.color = color;
return out;
}
@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
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;
}
+11 -22
View File
@@ -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,
+1 -1
View File
@@ -1,5 +1,5 @@
export interface Texture {
tex: WebGLTexture;
tex: GPUTexture;
width: number;
height: number;
}
+3 -17
View File
@@ -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<T extends Record<string, any>>(obj: T) {
+1 -1
View File
@@ -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": {