diff --git a/dev.py b/dev.py index 51fef0b..b12fbba 100644 --- a/dev.py +++ b/dev.py @@ -97,6 +97,7 @@ def main(): observer.stop() if _have_dev_dependencies: observer.join() + http_thread.join() if __name__ == "__main__": diff --git a/init.js b/init.js index 5ada403..dc092db 100644 --- a/init.js +++ b/init.js @@ -5,6 +5,8 @@ const files = [ "colormap.py", "compute.wgsl", "eval.wgsl", + "font.py", + "fonts.json", "gpu.py", "input_handler.py", "main.py", diff --git a/webgpu/main.py b/webgpu/main.py index 2685a20..1c96eb7 100644 --- a/webgpu/main.py +++ b/webgpu/main.py @@ -22,7 +22,9 @@ async def main(): gpu = await init_webgpu(js.document.getElementById("canvas")) - if 1: + point_number_object = None + + if 0: # create new ngsolve mesh and evaluate arbitrary function on it mesh = ngs.Mesh(unit_square.GenerateMesh(maxh=0.5)) order = 6 @@ -36,7 +38,7 @@ async def main(): # create testing mesh, this one also supports indexed or deferred rendering # but has always P1 and 'x' hard-coded as function query = urllib.parse.parse_qs(js.location.search[1:]) - N = 100 + N = 10 # N = int(5000/2**.5) # N = int(2000 / 2**0.5) # N = int(50/2**.5) @@ -49,6 +51,7 @@ async def main(): # mesh_object = MeshRenderObjectIndexed(gpu, buffers, n_trigs) # mesh_object = MeshRenderObjectDeferred(gpu, buffers, n_trigs) + point_number_object = PointNumbersRenderObject(gpu, buffers, font_size=16) # move mesh to center and scale it for i in [0, 5, 10]: gpu.uniforms.mat[i] = 1.8 @@ -87,6 +90,9 @@ def render(time): mesh_object.render(command_encoder) + if point_number_object is not None: + point_number_object.render(command_encoder) + gpu.device.queue.submit([command_encoder.finish()]) if frame_counter < 20: js.requestAnimationFrame(render_function) diff --git a/webgpu/mesh.py b/webgpu/mesh.py index 45c7641..b3e57c1 100644 --- a/webgpu/mesh.py +++ b/webgpu/mesh.py @@ -508,3 +508,89 @@ def create_testing_square_mesh(gpu, n): gpu.device.queue.submit([command_encoder.finish()]) return n_trigs, buffers + + +class PointNumbersRenderObject: + """Render a point numbers of a mesh""" + + def __init__(self, gpu, buffers, font_size=20): + + self._buffers = buffers + self.gpu = gpu + self.device = Device(gpu.device) + self.n_verts = self._buffers["vertices"].size // (4 * 3) + + self.set_font_size(font_size) + self.n_digits = 6 + + def get_bindings(self): + return [ + *self.gpu.uniforms.get_bindings(), + TextureBinding(Binding.FONT_TEXTURE, self.texture, dim=2), + BufferBinding(Binding.VERTICES, self._buffers["vertices"]), + ] + + def _create_pipeline(self): + bind_layout, self._bind_group = self.device.create_bind_group( + self.get_bindings(), "PointNumbersRenderObject" + ) + pipeline_layout = self.device.create_pipeline_layout(bind_layout) + shader_module = self.device.compile_files( + "webgpu/shader.wgsl", "webgpu/eval.wgsl" + ) + self._pipeline = self.gpu.device.createRenderPipeline( + to_js( + { + "label": "PointNumbersRenderObject", + "layout": pipeline_layout, + "vertex": { + "module": shader_module, + "entryPoint": "mainVertexPointNumber", + }, + "fragment": { + "module": shader_module, + "entryPoint": "mainFragmentText", + "targets": [ + { + "format": self.gpu.format, + "blend": { + "color": { + "operation": "add", + "srcFactor": "one", + "dstFactor": "one-minus-src-alpha", + }, + "alpha": { + "operation": "add", + "srcFactor": "one", + "dstFactor": "one-minus-src-alpha", + }, + }, + } + ], + }, + "primitive": { + "topology": "triangle-list", + "cullMode": "none", + "frontFace": "ccw", + }, + "depthStencil": self.gpu.depth_stencil, + } + ) + ) + + def render(self, encoder): + render_pass = self.gpu.begin_render_pass(encoder) + render_pass.setBindGroup(0, self._bind_group) + render_pass.setPipeline(self._pipeline) + render_pass.draw(self.n_digits * 6, self.n_verts, 0, 0) + render_pass.end() + + def set_font_size(self, font_size: int): + from .font import create_font_texture + + self.texture = create_font_texture(self.gpu.device, font_size) + char_width = self.texture.width // (127 - 32) + char_height = self.texture.height + self.gpu.uniforms.font_width = char_width + self.gpu.uniforms.font_height = char_height + self._create_pipeline() diff --git a/webgpu/shader.wgsl b/webgpu/shader.wgsl index 027707f..effa9fd 100644 --- a/webgpu/shader.wgsl +++ b/webgpu/shader.wgsl @@ -11,7 +11,11 @@ struct Uniforms { aspect: f32, eval_mode: u32, do_clipping: u32, - padding: u32, + font_width: u32, + font_height: u32, + padding0: u32, + padding1: u32, + padding2: u32, }; const VALUES_OFFSET: u32 = 2; // storing number of components and order of basis functions in first two entries @@ -28,7 +32,7 @@ const VALUES_OFFSET: u32 = 2; // storing number of components and order of basis @group(0) @binding(9) var index : array; @group(0) @binding(10) var gBufferLam : texture_2d; -// @group(0) @binding(11) var gBufferDepth : texture_depth_2d; +@group(0) @binding(11) var font : texture_2d; struct VertexOutput1d { @builtin(position) fragPosition: vec4, @@ -178,3 +182,73 @@ fn mainVertexDeferred(@builtin(vertex_index) vertexId: u32) -> VertexOutputDefer } +struct FragmentTextInput { + @builtin(position) fragPosition: vec4, + @location(0) tex_coord: vec2, +}; + +@vertex +fn mainVertexPointNumber(@builtin(vertex_index) vertexId: u32, @builtin(instance_index) pointId: u32) -> FragmentTextInput { + var p = vec3(vertices[3 * pointId], vertices[3 * pointId + 1], vertices[3 * pointId + 2]); + if uniforms.do_clipping != 0 { + if dot(uniforms.clipping_plane, vec4(p, 1.0)) < 0 { + return FragmentTextInput(vec4(-1.0, -1.0, 0.0, 1.0), vec2(0.)); + } + } + + var position = calcPosition(p); + let i_digit = vertexId / 6; + let vi = vertexId % 6; + + var length = 1u; + var n = 10u; + while n <= pointId + 1 { + length++; + n *= 10u; + } + + if i_digit >= length { + return FragmentTextInput(vec4(-1.0, -1.0, 0.0, 1.0), vec2(0.)); + } + + var digit = pointId + 1; + for (var i = 0u; i < i_digit; i++) { + digit = digit / 10; + } + digit = digit % 10; + + let w: f32 = 2 * f32(uniforms.font_width) / 1000.; + let h: f32 = 2 * f32(uniforms.font_height) / 800.; + + var tex_coord = vec2( + f32((digit + 16) * uniforms.font_width), + f32(uniforms.font_height) + ); + + if vi == 2 || vi == 4 || vi == 5 { + position.y += h * position.w; + tex_coord.y = 0.0; + } + + position.x += f32(length - i_digit -1) * w * position.w; + + if vi == 1 || vi == 2 || vi == 4 { + position.x += w * position.w; + tex_coord.x += f32(uniforms.font_width); + } + + return FragmentTextInput(position, tex_coord); +} + +@fragment +fn mainFragmentText(@location(0) tex_coord: vec2) -> @location(0) vec4 { + let alpha: f32 = textureLoad( + font, + vec2i(floor(tex_coord)), + 0 + ).x; + if alpha < 0.01 { + discard; + } + return vec4(0., 0., 0., alpha); +} diff --git a/webgpu/uniforms.py b/webgpu/uniforms.py index 9f3cbbd..2e55127 100644 --- a/webgpu/uniforms.py +++ b/webgpu/uniforms.py @@ -17,6 +17,7 @@ class Binding: VERTICES = 8 INDEX = 9 GBUFFERLAM = 10 + FONT_TEXTURE = 11 class ClippingPlaneUniform(ct.Structure): @@ -42,7 +43,11 @@ class Uniforms(ct.Structure): ("aspect", ct.c_float), ("eval_mode", ct.c_uint32), ("do_clipping", ct.c_uint32), - ("padding", ct.c_uint32), + ("font_width", ct.c_uint32), + ("font_height", ct.c_uint32), + ("padding0", ct.c_uint32), + ("padding1", ct.c_uint32), + ("padding2", ct.c_uint32), ] def __init__(self, device):