Skip to content

Commit eaaa1ea

Browse files
committed
Created Scene object
1 parent 07573cd commit eaaa1ea

8 files changed

Lines changed: 96 additions & 22 deletions

File tree

devlog.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
No new features for you! This time developing was dedicated almost entirely to refactoring our render pipeline so it's easier to use.
1+
We got some new optimizations, one of which provides a visual effect. Also, news!
22

3-
A graphics engine is pretty general-use, for once I have one up and running, I'm free to do essentially whatever I feel like. The problem is the quick graphics system I built yesterday is kind of bad.
4-
When I was writing my last devlog, I wanted to make a quick demo scene to show the rendering engine up and running. In doing so, I realized that the way I had structured my data pipeline was kind of all over the place, and required me to keep track of a bunch different buffers, that I was cloning and moving every draw frame. I took a quick look at my performance graph, and found that 6% of usage was in the `rasterize` function, which allocated memory every time a new frame was called.
3+
First of all, getting the purely optimizations things out of the way: we're stepping out of manually manipulating strings. I noticed that in my old benchmark, 8.2% of the time was being used in `build_3d_svg`. At first i found this to be weird, since this function is purely string manipulation, but, as it turns out, this is exactly the issue. String reallocation is expensive, and since they aren't mutable in Javascript, I can't just edit a pre-existing string.
4+
The solution to this is representing the strings as what they should be, character arrays. This involved the creation of a new helper class, StringBuffer, that represents strings as an uInt8 typed array, and the necessary helper functions to convert strings and numbers into this byte array method.
55

6-
We can't be having that. So, I decided to refactor my system to use a `Mesh` structure that holds three distinct buffers: our vertices, those vertices after being transformed, and them after being rasterized. This way, I allocate memory once, and modify these buffers at runtime, whenever needed.
7-
This required to rewrite _every_ step of my rendering pipeline, including most of our matrix math, to mutate instead of copy and return. This is particularly annoying, because javascript passes object by reference (good!) but you can't reassign them (bad), and it doesn't throw a warning if you try to.
6+
Another thing: I thought I wouldn't be able to use the `stroke` and `fill` attributes of svg because i thought this project could also be submitted to the #flavorless challenge. As it turns out, it isn't. So I get this freedom which, in turn, means I'll get to actually _color_ my 3D meshes.
7+
If you've done 3D rendering in OpenGL before, you'll likely be familiar with the GL_DEPTH_TESTING that you enable so two meshes in different z positions don't merge together. This is a nice bonus that comes pre-made, but, as with everything in our project, we had to implement ourselves through a technique called Painter's Algorithm. Usually, this is done through a technique called z-buffering, but this doesn't work for our svg based project, because it requires you to have pixels (while the only thing we have here are vertices).
88

9-
Anyways, everything is now done! This leaves us free to try to further optimize the `<svg>`, or pivot into actually doing anything with our engine.
10-
Attached, the new performance results!
9+
And, one last optimization: backface culling. We simply don't draw vertices facing away from the camera.
1110

12-
Obs: I thought this would be a quick refactor, and didn't plan ahead. No issues nor modularization of commits in this one :\(
13-
14-
**Commits**
15-
[Commit 5d1a342](https://github.com/Sekqies/native-html-images/commit/5d1a34267f30762c0e1550e14240dc450b4d9bc5): Refactor to use mesh system
11+
**Commits**
12+
[Commit f10e698](https://url.jam06452.uk/u0uj8u): Optimized string writing to use fixed-size buffer
13+
[Commit 07573cd](https://url.jam06452.uk/103qgwe): Depth sorting & Backface culling

src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { mat4, vec3 } from "./math/types";
22
import { perspective, identity, look_at, rotate, translate } from "./math/transformations";
33
import { create_sphere } from "./rendering/primitives";
4-
import { build_mesh } from "./rendering/render";
4+
import { build_mesh, build_meshes } from "./rendering/render";
55
import { StringBuffer } from "./utils/string_buffer";
66

77

@@ -28,7 +28,7 @@ export function main_3d() {
2828
planet_model = rotate(planet_model, time, vec3(0, 1, 0));
2929
planet_model = translate(planet_model, vec3(3.5, 0, 0));
3030
planet_model = rotate(planet_model, time * 3, vec3(1, 0, 1));
31-
frame_html += build_mesh(planet_mesh, planet_model,view,projection,string_buffer,do_wireframe,true);
31+
frame_html += build_meshes([sun_mesh,planet_mesh],[sun_model,planet_model],[view,view],[projection,projection],string_buffer,do_wireframe,true);
3232
target!.innerHTML = frame_html;
3333
requestAnimationFrame(loop);
3434
}

src/rendering/depth_sorting.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,5 @@ export function depth_sort(mesh: Mesh, stride: number = 4): number {
2020
visible_count++;
2121
}
2222

23-
const active_order = mesh.draw_order.subarray(0, visible_count);
24-
active_order.sort((a, b) => raster[b] - raster[a]);
25-
2623
return visible_count;
2724
}

src/rendering/mesh.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ export class Mesh{
1010
raster_end:number
1111
visible_triangles_count:number
1212

13-
constructor(vertices:ArrayType, indices:IndexingType){
13+
constructor(vertices:ArrayType, indices:IndexingType, raster_buffer:ArrayType | null = null){
1414
this.vertices = vertices;
1515
this.indices = indices;
1616
this.projected_buffer = new ArrayType(vertices.length * 4 / 3);
17-
this.raster_buffer = new ArrayType(indices.length * 4);
17+
if(raster_buffer === null)
18+
this.raster_buffer = new ArrayType(indices.length * 4);
19+
else
20+
this.raster_buffer = raster_buffer;
1821
this.draw_order = new IndexingType(indices.length);
1922
this.raster_end = indices.length * 4;
2023
this.visible_triangles_count = 0;

src/rendering/rasterizer.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import { process_perspective_mutate} from "./vertex";
99
* @param stride
1010
*/
1111
export function rasterize(mesh:Mesh, invert_y:boolean = true, stride:number = 4):void {
12-
const num_verts = mesh.raster_buffer.length/stride;
13-
const num_triangles = num_verts/3;
1412
const vertices = mesh.raster_buffer;
1513
const out = mesh.raster_buffer;
1614
let out_index = 0;

src/rendering/render.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { rasterize } from "./rasterizer";
22
import { build_3d_svg } from "../to_html";
33
import type { Mesh } from "./mesh";
44
import { mul_mat4 } from "../math/matrix_operators";
5-
import type { mat4 } from "../math/types";
5+
import { ArrayType, type mat4 } from "../math/types";
66
import { transform_vertices } from "./vertex";
77
import { assemble_primitives } from "./primitive_assembler";
88
import type { StringBuffer } from "../utils/string_buffer";
9+
import type { Scene } from "./scene";
910

1011
export function render(mesh: Mesh, model:mat4, view:mat4, projection:mat4, invert_y:boolean = true, stride:number = 4):void {
1112
const mvp = mul_mat4(mul_mat4(projection,view),model);
@@ -14,6 +15,16 @@ export function render(mesh: Mesh, model:mat4, view:mat4, projection:mat4, inver
1415
rasterize(mesh, invert_y, stride);
1516
}
1617

18+
export function render_scene(scene:Scene, mvp:mat4[], invert_y:boolean = true){
19+
for(let i = 0; i < scene.meshes.length; ++i){
20+
const mesh = scene.meshes[i];
21+
transform_vertices(mesh,mvp[i]);
22+
assemble_primitives(mesh);
23+
rasterize(mesh,invert_y);
24+
}
25+
}
26+
27+
1728
/**
1829
*
1930
* @param mesh The mesh to be rendered. Vertices and Indices must be filled.

src/rendering/scene.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { ArrayType, type IndexingType } from "../math/types";
2+
import { Mesh } from "./mesh";
3+
4+
export class Scene{
5+
scene_buffer:ArrayType;
6+
meshes:Mesh[];
7+
8+
constructor(vertices:ArrayType[], indices:IndexingType[]){
9+
if(indices.length !== vertices.length){
10+
console.warn("Scene built incorrectly: number of vertices is not equal to number of indices");
11+
}
12+
let vert_size = 0;
13+
let index_size = 0;
14+
for(let i = 0; i < vertices.length; ++i){
15+
vert_size += vertices[i].length;
16+
index_size += indices[i].length;
17+
}
18+
this.scene_buffer = new ArrayType(index_size * 4);
19+
this.meshes = new Array(vertices.length);
20+
let prev_length = 0;
21+
for(let i = 0; i < vertices.length; ++i){
22+
this.meshes[i] = new Mesh(vertices[i],indices[i],this.scene_buffer.subarray(prev_length,indices[i].length*4));
23+
}
24+
}
25+
resize(size:number){
26+
const prev = this.scene_buffer;
27+
this.scene_buffer = new ArrayType(size);
28+
this.scene_buffer.set(prev,0);
29+
}
30+
31+
}

src/to_html.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ function push_pair(x:number,y:number,buffer:StringBuffer){
122122

123123
const decoder = new TextDecoder('ascii');
124124

125-
export function build_3d_svg(vertices:ArrayType, end:number, use_rect:boolean, buffer:StringBuffer):string{
125+
export function build_3d_svg_legacy(vertices:ArrayType, end:number, use_rect:boolean, buffer:StringBuffer):string{
126126
const n = end;
127127
buffer.reset();
128128

@@ -156,4 +156,40 @@ export function build_3d_svg(vertices:ArrayType, end:number, use_rect:boolean, b
156156
return decoder.decode(buffer.buffer.subarray(0,buffer.cursor));
157157
}
158158

159+
const PATH_TOKENS = {
160+
HEAD: string_to_uint('<path d="'),
161+
M: string_to_uint('M '),
162+
L: string_to_uint('L '),
163+
Z: string_to_uint('Z '),
164+
TAIL_WIREFRAME: string_to_uint('" fill="none" stroke = "black" stroke-width = "0.005"/>'),
165+
TAIL_SOLID: string_to_uint('" stroke="red"/>')
166+
};
167+
168+
export function build_3d_svg(vertices:ArrayType, end:number, wireframe_mode:boolean,buffer:StringBuffer):string{
169+
buffer.reset();
170+
buffer.write_chunk(PATH_TOKENS.HEAD);
171+
for(let i = 0; i < end; i+=6){
172+
const x1 = vertices[i];
173+
const y1 = vertices[i+1];
159174

175+
const x2 = vertices[i+2];
176+
const y2 = vertices[i+3];
177+
178+
const x3 = vertices[i+4];
179+
const y3 = vertices[i+5];
180+
buffer.write_chunk(PATH_TOKENS.M);
181+
push_pair(x1,y1,buffer);
182+
buffer.write_chunk(PATH_TOKENS.L);
183+
push_pair(x2,y2,buffer);
184+
buffer.write_chunk(PATH_TOKENS.L);
185+
push_pair(x3,y3,buffer);
186+
buffer.write_chunk(PATH_TOKENS.Z);
187+
}
188+
if(wireframe_mode){
189+
buffer.write_chunk(PATH_TOKENS.TAIL_WIREFRAME);
190+
}
191+
else{
192+
buffer.write_chunk(PATH_TOKENS.TAIL_SOLID);
193+
}
194+
return decoder.decode(buffer.buffer.subarray(0,buffer.cursor));
195+
}

0 commit comments

Comments
 (0)