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
);
47 void csr_rt_save_tga( csr_target
*rt
, const char *path
, u32 offset
, u32 nc
);
48 void csr_rt_save_c32f( csr_target
*rt
, const char *path
, u32 offset
);
52 //=======================================================================================================================
57 csr_frag_program frag
;
79 const char *visgroup
; // Limit to this visgroup only
80 const char *classname
; // Limit to this exact classname. will not draw world
82 int compute_bounds_only
;
105 { 0x0.4p0f
, 0x0.4p0f
},
106 { 0x0.4p0f
, -0x0.4p0f
},
107 { -0x0.4p0f
, -0x0.4p0f
},
108 { -0x0.4p0f
, 0x0.4p0f
}
115 v2f csr_msaa_2x2rgss
[] =
117 { 0x0.2p0f
, 0x0.6p0f
},
118 { -0x0.6p0f
, 0x0.2p0f
},
119 { -0x0.2p0f
, -0x0.6p0f
},
120 { 0x0.6p0f
, -0x0.2p0f
}
131 v2f csr_msaa_8rook
[] =
133 { 0x0.1p0f
, 0x0.7p0f
},
134 { 0x0.5p0f
, 0x0.1p0f
},
135 { 0x0.7p0f
, -0x0.3p0f
},
136 { 0x0.3p0f
, -0x0.5p0f
},
137 { -0x0.1p0f
, -0x0.7p0f
},
138 { -0x0.5p0f
, -0x0.1p0f
},
139 { -0x0.7p0f
, 0x0.3p0f
},
140 { -0x0.3p0f
, 0x0.5p0f
}
144 void csr_create_target( csr_target
*rt
, u32 x
, u32 y
, EMSAA aa
, csr_shader
*shader
)
154 rt
->sample_src
= csr_msaa_1
;
159 rt
->sample_src
= csr_msaa_2x2
;
164 rt
->sample_src
= csr_msaa_2x2rgss
;
169 rt
->sample_src
= csr_msaa_8rook
;
174 rt
->depth
= (float *)csr_malloc( x
*y
*rt
->num_samples
* sizeof(float) );
175 rt
->colour
= csr_malloc( x
* y
* rt
->shader
->stride
* rt
->num_samples
);
177 v3_fill( rt
->bounds
[0], INFINITY
);
178 v3_fill( rt
->bounds
[1], -INFINITY
);
181 void csr_update_subsamples( csr_target
*rt
)
183 float range_x
= (rt
->bounds
[1][0]-rt
->bounds
[0][0]);
184 float range_y
= (rt
->bounds
[1][1]-rt
->bounds
[0][1]);
186 v2f pixel_size
= { range_x
/(float)rt
->x
, range_y
/(float)rt
->y
};
188 for( int i
= 0; i
< rt
->num_samples
; i
++ )
190 v2_mul( rt
->sample_src
[i
], pixel_size
, rt
->subsamples
[i
] );
194 void csr_rt_free( csr_target
*rt
)
200 void csr_rt_clear( csr_target
*rt
)
202 for( u32 i
= 0; i
< rt
->x
*rt
->y
*rt
->num_samples
; i
++ )
204 rt
->shader
->clear( rt
->colour
+ i
* rt
->shader
->stride
);
209 void csr_auto_fit( csr_target
*rt
, float padding
)
211 // Correct aspect ratio to be square
212 float dx
, dy
, l
, cx
, cy
;
214 dx
= rt
->bounds
[1][0] - rt
->bounds
[0][0];
215 dy
= rt
->bounds
[1][1] - rt
->bounds
[0][1];
217 l
= fmaxf( dx
, dy
) * .5f
;
219 cx
= (rt
->bounds
[1][0] + rt
->bounds
[0][0]) * .5f
;
220 cy
= (rt
->bounds
[1][1] + rt
->bounds
[0][1]) * .5f
;
222 rt
->bounds
[0][0] = cx
- l
- padding
;
223 rt
->bounds
[1][0] = cx
+ l
+ padding
;
224 rt
->bounds
[0][1] = cy
- l
- padding
;
225 rt
->bounds
[1][1] = cy
+ l
+ padding
;
227 rt
->scale
= l
+ padding
;
229 csr_update_subsamples( rt
);
232 void simple_raster( csr_target
*rt
, vmf_vert tri
[3] )
234 // Very very simplified rasterizing algorithm
235 v2f bmin
= { 0.f
, 0.f
};
236 v2f bmax
= { rt
->x
, rt
->y
};
238 v2_minv( tri
[0].co
, tri
[1].co
, bmin
);
239 v2_minv( tri
[2].co
, bmin
, bmin
);
241 v2_maxv( tri
[0].co
, tri
[1].co
, bmax
);
242 v2_maxv( tri
[2].co
, bmax
, bmax
);
244 float range_x
= (rt
->bounds
[1][0]-rt
->bounds
[0][0])/(float)rt
->x
;
245 float range_y
= (rt
->bounds
[1][1]-rt
->bounds
[0][1])/(float)rt
->y
;
247 int start_x
= csr_min( rt
->x
-1, csr_max( 0, floorf( (bmin
[0]-rt
->bounds
[0][0])/range_x
)));
248 int end_x
= csr_max( 0, csr_min( rt
->x
-1, ceilf( (bmax
[0]-rt
->bounds
[0][0])/range_x
)));
249 int start_y
= csr_min( rt
->y
-1, csr_max( 0, floorf( (bmin
[1]-rt
->bounds
[0][1])/range_y
)));
250 int end_y
= csr_max( 0, csr_min( rt
->y
-1, ceilf( (bmax
[1]-rt
->bounds
[0][1])/range_y
)));
253 float d
, bca
= 0.f
, bcb
= 0.f
, bcc
= 0.f
;
255 v2_sub( tri
[1].co
, tri
[0].co
, v0
);
256 v2_sub( tri
[2].co
, tri
[0].co
, v1
);
257 v2_sub( tri
[1].co
, tri
[2].co
, v2
);
258 d
= 1.f
/ (v0
[0]*v1
[1] - v1
[0]*v0
[1]);
261 if( v2_cross( v0
, v1
) > 0.f
)
266 for( u32 py
= start_y
; py
<= end_y
; py
++ )
268 trace_origin
[1] = csr_lerpf( rt
->bounds
[0][1], rt
->bounds
[1][1], (float)py
/(float)rt
->y
);
270 for( u32 px
= start_x
; px
<= end_x
; px
++ )
272 u32 sample_index
= ((rt
->y
-py
-1)*rt
->x
+px
) * rt
->num_samples
;
274 void *frag
= rt
->colour
+ sample_index
*rt
->shader
->stride
;
275 float *depth
= &rt
->depth
[ sample_index
];
277 trace_origin
[0] = csr_lerpf( rt
->bounds
[0][0], rt
->bounds
[1][0], (float)px
/(float)rt
->x
);
279 // Determine coverage
280 for( int i
= 0; i
< rt
->num_samples
; i
++ )
284 v2_add( rt
->subsamples
[ i
], trace_origin
, sample_origin
);
285 v2_sub( sample_origin
, tri
[0].co
, vp
);
287 if( v2_cross( v0
, vp
) > 0.f
)
289 if( v2_cross( vp
, v1
) > 0.f
)
293 v2_sub( sample_origin
, tri
[2].co
, vp2
);
295 if( v2_cross( vp2
, v2
) > 0.f
)
298 bcb
= (vp
[0]*v1
[1] - v1
[0]*vp
[1]) * d
;
299 bcc
= (v0
[0]*vp
[1] - vp
[0]*v0
[1]) * d
;
300 bca
= 1.f
- bcb
- bcc
;
302 float hit
= tri
[0].co
[2] * bca
+ tri
[1].co
[2] * bcb
+ tri
[2].co
[2] * bcc
;
303 float hit_depth
= hit
+ 16385.f
;
305 if( hit_depth
> depth
[i
] && hit
>= rt
->bounds
[0][2] && hit
<= rt
->bounds
[1][2] )
307 depth
[i
] = hit_depth
;
308 rt
->shader
->frag( frag
+i
*rt
->shader
->stride
, tri
, bca
, bcb
, bcc
);
315 void csr_draw( csr_target
*rt
, vmf_vert
*triangles
, u32 triangle_count
, m4x3f transform
)
320 // Derive normal matrix
321 m4x3_to_3x3( transform
, normal
);
322 m3x3_inv_transpose( normal
, normal
);
324 for( u32 i
= 0; i
< triangle_count
; i
++ )
326 vmf_vert
*triangle
= triangles
+ i
*3;
328 m4x3_mulv( transform
, triangle
[0].co
, new_tri
[0].co
);
329 m4x3_mulv( transform
, triangle
[1].co
, new_tri
[1].co
);
330 m4x3_mulv( transform
, triangle
[2].co
, new_tri
[2].co
);
332 m3x3_mulv( normal
, triangle
[0].nrm
, new_tri
[0].nrm
);
333 m3x3_mulv( normal
, triangle
[1].nrm
, new_tri
[1].nrm
);
334 m3x3_mulv( normal
, triangle
[2].nrm
, new_tri
[2].nrm
);
336 v3_normalize( new_tri
[0].nrm
);
337 v3_normalize( new_tri
[1].nrm
);
338 v3_normalize( new_tri
[2].nrm
);
340 m4x3_mulv( transform
, triangles
[0].origin
, new_tri
[0].origin
);
342 simple_raster( rt
, new_tri
);
346 void csr_vmf_render( csr_target
*rt
, vmf_map
*map
, vdf_node
*root
, csr_filter
*filter
, m4x3f prev
, m4x3f inst
)
348 m4x3f transform
= M4X3_IDENTITY
;
354 int filter_visgroups
= 0, filter_classname
= 0, compute_bounds_only
= 0;
358 if( filter
->visgroup
)
360 filter_visgroups
= 1;
361 group_id
= vmf_visgroup_id( root
, filter
->visgroup
);
364 if( filter
->classname
)
366 filter_classname
= 1;
369 compute_bounds_only
= filter
->compute_bounds_only
;
372 // Multiply previous transform with instance transform to create basis
375 m4x3_mul( prev
, inst
, transform
);
378 // Gather world brushes
379 solidgen_ctx_init( &solid
);
381 if( !filter_classname
)
383 vdf_node
*world
= vdf_next( root
, "world", NULL
);
385 vdf_foreach( world
, "solid", brush
)
387 if( filter_visgroups
&& !vmf_visgroup_match( brush
, group_id
) )
390 // TODO: heap-use-after-free
391 solidgen_push( &solid
, brush
);
395 // Actual entity loop
398 vdf_foreach( root
, "entity", ent
)
400 if( filter_visgroups
&& !vmf_visgroup_match( ent
, group_id
) )
403 if( filter_classname
)
404 if( strcmp( kv_get( ent
, "classname", "" ), filter
->classname
) )
407 if( ent
->user
& VMF_FLAG_IS_PROP
)
409 // Create model transform
410 m4x3_identity( model
);
412 vmf_entity_transform( ent
, model
);
413 m4x3_mul( transform
, model
, model
);
416 mdl_mesh_t
*mdl
= &map
->models
[ ent
->user1
].mdl
;
418 if( compute_bounds_only
)
420 map
->models
[ ent
->user1
].need_load
= 1;
421 m4x3_expand_aabb_point( model
, rt
->bounds
, (v3f
){0.f
,0.f
,0.f
} );
425 for( int i
= 0; i
< mdl
->num_indices
/3; i
++ )
427 for( int j
= 0; j
< 3; j
++ )
429 v3_copy( &mdl
->vertices
[ mdl
->indices
[ i
*3+j
] *8 ], tri
[j
].co
);
430 v3_copy( &mdl
->vertices
[ mdl
->indices
[ i
*3+j
] *8+3 ], tri
[j
].nrm
);
431 v3_zero( tri
[j
].origin
);
434 csr_draw( rt
, tri
, 1, model
);
438 else if( ent
->user
& VMF_FLAG_IS_INSTANCE
)
440 m4x3_identity( model
);
441 vmf_entity_transform( ent
, model
);
443 csr_vmf_render( rt
, map
, map
->cache
[ ent
->user1
].root
, filter
, transform
, model
);
448 vdf_foreach( ent
, "solid", ent_solid
)
450 solidgen_push( &solid
, ent_solid
);
455 if( compute_bounds_only
)
457 solidgen_bounds( &solid
, trf_bounds
);
458 m4x3_transform_aabb( transform
, trf_bounds
);
459 box_concat( rt
->bounds
, trf_bounds
);
464 for( int i
= 0; i
< csr_sb_count( solid
.indices
)/3; i
++ )
466 u32
* base
= solid
.indices
+ i
*3;
468 tri
[0] = solid
.verts
[ base
[0] ];
469 tri
[1] = solid
.verts
[ base
[1] ];
470 tri
[2] = solid
.verts
[ base
[2] ];
472 csr_draw( rt
, tri
, 1, transform
);
476 solidgen_ctx_reset( &solid
);
477 solidgen_ctx_free( &solid
);
480 void csr_write_filerr( const char *path
)
482 log_error( "File write error (No such file or directory): '%s'\n", path
);
485 void csr_write_txt( char const *path
, const char *name
, csr_target
*rt
)
489 write_ptr
= fopen( path
, "w" );
493 fprintf( write_ptr
, "\"%s\"\n\{\n", name
);
494 fprintf( write_ptr
, "\t\"material\" \"overviews/%s\"\n", name
);
495 fprintf( write_ptr
, "\t\"pos_x\" \"%.8f\"\n", rt
->bounds
[0][0] );
496 fprintf( write_ptr
, "\t\"pos_y\" \"%.8f\"\n", rt
->bounds
[0][1] );
497 fprintf( write_ptr
, "\t\"scale\" \"%.8f\"\n", rt
->scale
/ (float)rt
->x
);
498 fprintf( write_ptr
, "}\n" );
504 csr_write_filerr( path
);
509 void csr_rt_save_c32f( csr_target
*rt
, const char *path
, u32 offset
)
511 float *image
= (float *)csr_malloc( rt
->x
*rt
->y
*3*sizeof(float) );
513 float contrib
= 1.f
/(float)rt
->num_samples
;
515 for( int i
= 0; i
< rt
->x
*rt
->y
; i
++ )
517 void *src
= rt
->colour
+ offset
+ i
* rt
->num_samples
* rt
->shader
->stride
;
518 float *dst
= image
+ i
*3;
521 for( int k
= 0; k
< rt
->num_samples
; k
++ )
523 v3_muladds( dst
, (float *)(src
+ k
*rt
->shader
->stride
), contrib
, dst
);
527 if( !csr_32f_write( path
, rt
->x
, rt
->y
, image
) )
528 csr_write_filerr( path
);
533 // Save floating point buffer to tga. Must be in range (0-1)
534 // Offset and stride are in bytes
535 void csr_rt_save_tga( csr_target
*rt
, const char *path
, u32 offset
, u32 nc
)
537 u8
*image
= (u8
*)csr_malloc( rt
->x
*rt
->y
* 4 );
539 float contrib
= 255.f
/(float)rt
->num_samples
;
541 for( int i
= 0; i
< rt
->x
*rt
->y
; i
++ )
543 void *src
= rt
->colour
+ offset
+ i
* rt
->num_samples
* rt
->shader
->stride
;
544 u8
*dst
= image
+ i
*4;
546 v4f accum
= { 0.f
, 0.f
, 0.f
, 0.f
};
548 for( int k
= 0; k
< rt
->num_samples
; k
++ )
550 float *src_sample
= (float *)(src
+ k
*rt
->shader
->stride
);
552 for( int j
= 0; j
< nc
; j
++ )
554 accum
[ j
] += src_sample
[ j
] * contrib
;
558 // TODO: Clamp this value
565 if( !csr_tga_write( path
, rt
->x
, rt
->y
, nc
, image
) )
566 csr_write_filerr( path
);