1 // Copyright (C) 2021 Harry Godden (hgn)
3 // Extremely simple software renderer. Only supports orthographic
4 //=======================================================================================================================
6 typedef struct csr_target csr_target
;
7 typedef struct csr_filter csr_filter
;
8 typedef struct csr_shader csr_shader
;
9 typedef enum EMSAA EMSAA
;
11 typedef void (* csr_frag_program
)( void *, vmf_vert
[3], float, float, float );
12 typedef void (* csr_frag_clear
)( void * );
15 //=======================================================================================================================
17 // Create a render target. Resolution, msaa, and shader must be known at this point!
18 void csr_create_target( csr_target
*rt
, u32 x
, u32 y
, EMSAA aa
, csr_shader
*shader
);
19 void csr_rt_clear( csr_target
*rt
);
20 void csr_rt_free( csr_target
*rt
);
22 // Refit bounds so that it is square, and crops to center with padding
23 void csr_auto_fit( csr_target
*rt
, float padding
);
25 // Run this after bounds have been adjusted on the RT to update the size of the msaa
26 // Auto fit will call this.
27 void csr_update_subsamples( csr_target
*rt
);
29 // Write CS:GO radar txt
30 void csr_write_txt( char const *path
, const char *name
, csr_target
*rt
);
35 // Render a finalzied triangle into target. Coordinates are world space
36 void simple_raster( csr_target
*rt
, vmf_vert tri
[3] );
38 // Draw a batch of triangles with an affine world transformation
39 void csr_draw( csr_target
*rt
, vmf_vert
*triangles
, u32 triangle_count
, m4x3f transform
);
41 // Draw VMF with filtering options. Will automatically branch into instances
42 // You should call this with the last two recursive arguments (prev,inst), set to NULL
44 // Filter is optional, it can be st to NULL to just render everything.
45 void csr_vmf_render( csr_target
*rt
, vmf_map
*map
, vdf_node
*root
, csr_filter
*filter
, m4x3f prev
, m4x3f inst
);
48 void csr_rt_save_buffers( csr_target
*rt
, const char *basename
, const char *subname
);
51 //=======================================================================================================================
56 csr_frag_program frag
;
78 const char *visgroup
; // Limit to this visgroup only
79 const char *classname
; // Limit to this exact classname. will not draw world
81 int compute_bounds_only
;
104 { 0x0.4p0f
, 0x0.4p0f
},
105 { 0x0.4p0f
, -0x0.4p0f
},
106 { -0x0.4p0f
, -0x0.4p0f
},
107 { -0x0.4p0f
, 0x0.4p0f
}
114 v2f csr_msaa_2x2rgss
[] =
116 { 0x0.2p0f
, 0x0.6p0f
},
117 { -0x0.6p0f
, 0x0.2p0f
},
118 { -0x0.2p0f
, -0x0.6p0f
},
119 { 0x0.6p0f
, -0x0.2p0f
}
130 v2f csr_msaa_8rook
[] =
132 { 0x0.1p0f
, 0x0.7p0f
},
133 { 0x0.5p0f
, 0x0.1p0f
},
134 { 0x0.7p0f
, -0x0.3p0f
},
135 { 0x0.3p0f
, -0x0.5p0f
},
136 { -0x0.1p0f
, -0x0.7p0f
},
137 { -0x0.5p0f
, -0x0.1p0f
},
138 { -0x0.7p0f
, 0x0.3p0f
},
139 { -0x0.3p0f
, 0x0.5p0f
}
143 void csr_create_target( csr_target
*rt
, u32 x
, u32 y
, EMSAA aa
, csr_shader
*shader
)
153 rt
->sample_src
= csr_msaa_1
;
158 rt
->sample_src
= csr_msaa_2x2
;
163 rt
->sample_src
= csr_msaa_2x2rgss
;
168 rt
->sample_src
= csr_msaa_8rook
;
173 rt
->depth
= (float *)csr_malloc( x
*y
*rt
->num_samples
* sizeof(float) );
174 rt
->colour
= csr_malloc( x
* y
* rt
->shader
->stride
* rt
->num_samples
);
176 v3_fill( rt
->bounds
[0], INFINITY
);
177 v3_fill( rt
->bounds
[1], -INFINITY
);
180 void csr_update_subsamples( csr_target
*rt
)
182 float range_x
= (rt
->bounds
[1][0]-rt
->bounds
[0][0]);
183 float range_y
= (rt
->bounds
[1][1]-rt
->bounds
[0][1]);
185 v2f pixel_size
= { range_x
/(float)rt
->x
, range_y
/(float)rt
->y
};
187 for( int i
= 0; i
< rt
->num_samples
; i
++ )
189 v2_mul( rt
->sample_src
[i
], pixel_size
, rt
->subsamples
[i
] );
193 void csr_rt_free( csr_target
*rt
)
199 void csr_rt_clear( csr_target
*rt
)
201 for( u32 i
= 0; i
< rt
->x
*rt
->y
*rt
->num_samples
; i
++ )
203 rt
->shader
->clear( rt
->colour
+ i
* rt
->shader
->stride
);
208 void csr_auto_fit( csr_target
*rt
, float padding
)
210 // Correct aspect ratio to be square
211 float dx
, dy
, l
, cx
, cy
;
213 dx
= rt
->bounds
[1][0] - rt
->bounds
[0][0];
214 dy
= rt
->bounds
[1][1] - rt
->bounds
[0][1];
216 l
= fmaxf( dx
, dy
) * .5f
;
218 cx
= (rt
->bounds
[1][0] + rt
->bounds
[0][0]) * .5f
;
219 cy
= (rt
->bounds
[1][1] + rt
->bounds
[0][1]) * .5f
;
221 rt
->bounds
[0][0] = cx
- l
- padding
;
222 rt
->bounds
[1][0] = cx
+ l
+ padding
;
223 rt
->bounds
[0][1] = cy
- l
- padding
;
224 rt
->bounds
[1][1] = cy
+ l
+ padding
;
226 rt
->scale
= l
+ padding
;
228 csr_update_subsamples( rt
);
231 void csr_write_txt( char const *path
, const char *name
, csr_target
*rt
)
235 write_ptr
= fopen( path
, "w" );
237 fprintf( write_ptr
, "\"%s\"\n\{\n", name
);
238 fprintf( write_ptr
, "\t\"material\" \"overviews/%s\"\n", name
);
239 fprintf( write_ptr
, "\t\"pos_x\" \"%.8f\"\n", rt
->bounds
[0][0] );
240 fprintf( write_ptr
, "\t\"pos_y\" \"%.8f\"\n", rt
->bounds
[0][1] );
241 fprintf( write_ptr
, "\t\"scale\" \"%.8f\"\n", rt
->scale
/ (float)rt
->x
);
242 fprintf( write_ptr
, "}\n" );
247 void simple_raster( csr_target
*rt
, vmf_vert tri
[3] )
249 // Very very simplified rasterizing algorithm
250 v2f bmin
= { 0.f
, 0.f
};
251 v2f bmax
= { rt
->x
, rt
->y
};
253 v2_minv( tri
[0].co
, tri
[1].co
, bmin
);
254 v2_minv( tri
[2].co
, bmin
, bmin
);
256 v2_maxv( tri
[0].co
, tri
[1].co
, bmax
);
257 v2_maxv( tri
[2].co
, bmax
, bmax
);
259 float range_x
= (rt
->bounds
[1][0]-rt
->bounds
[0][0])/(float)rt
->x
;
260 float range_y
= (rt
->bounds
[1][1]-rt
->bounds
[0][1])/(float)rt
->y
;
262 int start_x
= csr_min( rt
->x
-1, csr_max( 0, floorf( (bmin
[0]-rt
->bounds
[0][0])/range_x
)));
263 int end_x
= csr_max( 0, csr_min( rt
->x
-1, ceilf( (bmax
[0]-rt
->bounds
[0][0])/range_x
)));
264 int start_y
= csr_min( rt
->y
-1, csr_max( 0, floorf( (bmin
[1]-rt
->bounds
[0][1])/range_y
)));
265 int end_y
= csr_max( 0, csr_min( rt
->y
-1, ceilf( (bmax
[1]-rt
->bounds
[0][1])/range_y
)));
268 float d
, bca
= 0.f
, bcb
= 0.f
, bcc
= 0.f
;
270 v2_sub( tri
[1].co
, tri
[0].co
, v0
);
271 v2_sub( tri
[2].co
, tri
[0].co
, v1
);
272 v2_sub( tri
[1].co
, tri
[2].co
, v2
);
273 d
= 1.f
/ (v0
[0]*v1
[1] - v1
[0]*v0
[1]);
276 if( v2_cross( v0
, v1
) > 0.f
)
281 for( u32 py
= start_y
; py
<= end_y
; py
++ )
283 trace_origin
[1] = csr_lerpf( rt
->bounds
[0][1], rt
->bounds
[1][1], (float)py
/(float)rt
->y
);
285 for( u32 px
= start_x
; px
<= end_x
; px
++ )
287 u32 sample_index
= (py
* rt
->y
+ px
) * rt
->num_samples
;
289 void *frag
= rt
->colour
+ sample_index
*rt
->shader
->stride
;
290 float *depth
= &rt
->depth
[ sample_index
];
292 trace_origin
[0] = csr_lerpf( rt
->bounds
[0][0], rt
->bounds
[1][0], (float)px
/(float)rt
->x
);
294 // Determine coverage
295 for( int i
= 0; i
< rt
->num_samples
; i
++ )
299 v2_add( rt
->subsamples
[ i
], trace_origin
, sample_origin
);
300 v2_sub( sample_origin
, tri
[0].co
, vp
);
302 if( v2_cross( v0
, vp
) > 0.f
)
304 if( v2_cross( vp
, v1
) > 0.f
)
308 v2_sub( sample_origin
, tri
[2].co
, vp2
);
310 if( v2_cross( vp2
, v2
) > 0.f
)
313 bcb
= (vp
[0]*v1
[1] - v1
[0]*vp
[1]) * d
;
314 bcc
= (v0
[0]*vp
[1] - vp
[0]*v0
[1]) * d
;
315 bca
= 1.f
- bcb
- bcc
;
317 float hit
= (tri
[0].co
[2] * bca
+ tri
[1].co
[2] * bcb
+ tri
[2].co
[2] * bcc
) +16385.f
;
322 rt
->shader
->frag( frag
+i
*rt
->shader
->stride
, tri
, bca
, bcb
, bcc
);
329 void csr_draw( csr_target
*rt
, vmf_vert
*triangles
, u32 triangle_count
, m4x3f transform
)
334 // Derive normal matrix
335 m4x3_to_3x3( transform
, normal
);
337 // NOTE: This isn't strictly necessary since CS:GO only uses uniform scaling.
338 m3x3_inv_transpose( normal
, normal
);
340 for( u32 i
= 0; i
< triangle_count
; i
++ )
342 vmf_vert
*triangle
= triangles
+ i
*3;
344 m4x3_mulv( transform
, triangle
[0].co
, new_tri
[0].co
);
345 m4x3_mulv( transform
, triangle
[1].co
, new_tri
[1].co
);
346 m4x3_mulv( transform
, triangle
[2].co
, new_tri
[2].co
);
347 m3x3_mulv( normal
, triangle
[0].nrm
, new_tri
[0].nrm
);
348 m3x3_mulv( normal
, triangle
[1].nrm
, new_tri
[1].nrm
);
349 m3x3_mulv( normal
, triangle
[2].nrm
, new_tri
[2].nrm
);
351 simple_raster( rt
, new_tri
);
355 void csr_vmf_render( csr_target
*rt
, vmf_map
*map
, vdf_node
*root
, csr_filter
*filter
, m4x3f prev
, m4x3f inst
)
357 m4x3f transform
= M4X3_IDENTITY
;
363 int filter_visgroups
= 0, filter_classname
= 0, compute_bounds_only
= 0;
367 if( filter
->visgroup
)
369 filter_visgroups
= 1;
370 group_id
= vmf_visgroup_id( root
, filter
->visgroup
);
373 if( filter
->classname
)
375 filter_classname
= 1;
378 compute_bounds_only
= filter
->compute_bounds_only
;
381 // Multiply previous transform with instance transform to create basis
384 m4x3_mul( prev
, inst
, transform
);
387 // Gather world brushes
388 solidgen_ctx_init( &solid
);
390 if( !filter_classname
)
392 vdf_node
*world
= vdf_next( root
, "world", NULL
);
394 vdf_foreach( world
, "solid", brush
)
396 if( filter_visgroups
&& !vmf_visgroup_match( brush
, group_id
) )
399 // TODO: heap-use-after-free
400 solidgen_push( &solid
, brush
);
404 // Actual entity loop
407 vdf_foreach( root
, "entity", ent
)
409 if( filter_visgroups
&& !vmf_visgroup_match( ent
, group_id
) )
412 if( filter_classname
)
413 if( strcmp( kv_get( ent
, "classname", "" ), filter
->classname
) )
416 if( ent
->user
& VMF_FLAG_IS_PROP
)
418 // Create model transform
419 m4x3_identity( model
);
421 vmf_entity_transform( ent
, model
);
422 m4x3_mul( transform
, model
, model
);
425 mdl_mesh_t
*mdl
= &map
->models
[ ent
->user1
].mdl
;
427 if( compute_bounds_only
)
429 map
->models
[ ent
->user1
].need_load
= 1;
430 m4x3_expand_aabb_point( model
, rt
->bounds
, (v3f
){0.f
,0.f
,0.f
} );
434 for( int i
= 0; i
< mdl
->num_indices
/3; i
++ )
436 for( int j
= 0; j
< 3; j
++ )
438 v3_copy( &mdl
->vertices
[ mdl
->indices
[ i
*3+j
] *8 ], tri
[j
].co
);
439 v3_copy( &mdl
->vertices
[ mdl
->indices
[ i
*3+j
] *8+3 ], tri
[j
].nrm
);
444 csr_draw( rt
, tri
, 1, model
);
448 else if( ent
->user
& VMF_FLAG_IS_INSTANCE
)
450 m4x3_identity( model
);
451 vmf_entity_transform( ent
, model
);
453 csr_vmf_render( rt
, map
, map
->cache
[ ent
->user1
].root
, filter
, transform
, model
);
458 vdf_foreach( ent
, "solid", ent_solid
)
460 solidgen_push( &solid
, ent_solid
);
465 if( compute_bounds_only
)
467 solidgen_bounds( &solid
, trf_bounds
);
468 m4x3_transform_aabb( transform
, trf_bounds
);
469 box_concat( rt
->bounds
, trf_bounds
);
474 for( int i
= 0; i
< csr_sb_count( solid
.indices
)/3; i
++ )
476 u32
* base
= solid
.indices
+ i
*3;
478 tri
[0] = solid
.verts
[ base
[0] ];
479 tri
[1] = solid
.verts
[ base
[1] ];
480 tri
[2] = solid
.verts
[ base
[2] ];
482 csr_draw( rt
, tri
, 1, transform
);
486 solidgen_ctx_reset( &solid
);
487 solidgen_ctx_free( &solid
);
491 void csr_rt_save_buffers( csr_target
*rt
, const char *basename
, const char *subname
)
495 float *image
= (float *)csr_malloc( 1024*1024*sizeof(float)*3 );
497 float contrib
= 1.f
/(float)rt
->num_samples
;
499 for( int l
= 0; l
< rt
->x
; l
++ )
501 for( int x
= 0; x
< rt
->y
; x
++ )
503 float *dst
= &image
[ (l
*1024+x
)*3 ];
504 void *src
= rt
->colour
+ ((1023-l
)*1024+x
) * rt
->num_samples
* rt
->shader
->stride
;
506 v3_muls( (float *)src
, contrib
, dst
);
508 for( int j
= 1; j
< rt
->num_samples
; j
++ )
510 v3_muladds( dst
, (float *)(src
+ j
*rt
->shader
->stride
), contrib
, dst
);
515 // Save position buffer
516 strcpy( output
, basename
);
517 strcat( output
, "." );
518 strcat( output
, subname
);
519 strcat( output
, "_position.pfm" );
520 csr_32f_write( output
, rt
->x
, rt
->y
, image
);